Hi,

I'm new to the list and at the moment the archives seem to be a bit
broken, so my apologies if this is the wrong protocol to submit an
addition.

I've been using Log4Net under Mono on Linux and MacOSX for some of my
research and quickly became jealous that there was no way to get the
ColoredConsoleAppender under these platforms because of the use of Win32
APIs.  However, it's not difficult to do color on these platforms, just
send out ANSI escape sequences.

With that in mind, I present AnsiColoredConsoleAppender.  Just drop it
in with the other Appenders and recompile log4net and you can use it. 
Syntax works about the same as ColoredConsoleAppender with the exception
that foreColor and backColor can only be a color (none of the
HighIntensity stuff).  There also is a new tag, "attrs", that can be one
of Bright, Dim, Underscore, Blink, Reverse, Hidden, or Strikethrough.

Needless to say, it's based heavily on the ColoredConsoleAppender and
ConsoleAppender.  So props to Rick Hobbs and Nicko Cadell.

I've attached the code to this message, but if the list strips it out,
you can also find it at:
 
http://patrick.wagstrom.net/projects/personal/log4net/AnsiColoredConsoleAppender.cs

Feedback would be appreciated.

Thanks,

Patrick Wagstrom
using System;
using System.Globalization;
using System.Runtime.InteropServices;

using log4net.Layout;

namespace log4net.Appender
{
	/// <summary>
	/// Appends logging events to the console with ANSI escape sequences.
	/// </summary>
	/// <remarks>
	/// <para>
	/// AnsiColoredConsoleAppender 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
	/// programatically redirected (for example NUnit does this to capture program ouput).
	/// When this output is redirected it will look funny because the ANSI escape codes will
	/// also be captured.  If you want to respect these redirections the <see cref="ConsoleAppender"/>
	/// must be used.
	/// </para>
	/// <para>
	/// When configuring the ANSI colored console appender, mapping should be
	/// specified to map a logging level to a color. For example:
	/// </para>
	/// <code>
	/// &lt;mapping&gt;
	/// 	&lt;level value="ERROR" /&gt;
	/// 	&lt;foreColor value="White" /&gt;
	/// 	&lt;backColor value="Red" /&gt;
	///     &lt;attrs value="Bright" /&gt;
	/// &lt;/mapping&gt;
	/// &lt;mapping&gt;
	/// 	&lt;level value="DEBUG" /&gt;
	/// 	&lt;backColor value="Green" /&gt;
	/// &lt;/mapping&gt;
	/// </code>
	/// <para>
	/// The Level is the standard log4net logging level and ForeColor and BackColor can be any
	/// combination of:
	/// <list type="bullet">
	/// <item><term>Blue</term><description>color is blue</description></item>
	/// <item><term>Green</term><description>color is red</description></item>
	/// <item><term>Red</term><description>color is green</description></item>
	/// <item><term>White</term><description>color is white</description></item>
	/// <item><term>Yellow</term><description>color is yellow</description></item>
	/// <item><term>Purple</term><description>color is purple</description></item>
	/// <item><term>Cyan</term><description>color is cyan</description></item>
	/// </list>
	/// </para>
	/// <para>
	/// The attributes can be any of the following:
	/// <list type="bullet">
	/// <item><term>Bright</term><description>foreground is brighter</description></item>
	/// <item><term>Dim</term><description>foreground is dimmer</description></item>
	/// <item><term>Underscore</term><description>message is underlines</description></item>
	/// <item><term>Blink</term><description>foreground is blinking (does not work on all terminals)</description></item>
	/// <item><term>Reverse</term><description>foreground and background are reversed</description></item>
	/// <item><term>Hidden</term><description>output is hidden</description></item>
	/// <item><term>Strikethrough</term></description>message has a line through it</description></item>
	/// </para>
	/// <para>
	/// This code is based heavily on the code for ColoredConsoleAppender by Rick Hobbs and Nicko Cadell.
	/// </para>
	/// </remarks>
	/// <author>Patrick Wagstrom</author>
	public class AnsiColoredConsoleAppender : AppenderSkeleton
	{
		#region Colors Enum

		/// <summary>
		/// The enum of possible color values for use with the color mapping method
		/// </summary>
		[Flags]
		public enum Attributes : int
		{
			Reset = 0,
			Bright = 1,
			Dim = 2,
			Underscore = 4,
			Blink = 5,
			Reverse = 7,
			Hidden = 8,
			Strikethrough = 9
		}

		[Flags]
		public enum Foregrounds : int
		{
			Black = 30,
			Red = 31,
			Green = 32,
			Yellow = 33,
			Blue = 34,
			Magenta = 35,
			Cyan = 36,
			White = 37
		}

		[Flags]
		public enum Backgrounds : int
		{
			Black = 40,
			Red = 41,
			Green = 42,
			Yellow = 43,
			Blue = 44,
			Magenta = 45,
			Cyan = 46,
			White = 47
		}
		#endregion

		#region Public Instance Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="AnsiColoredConsoleAppender" /> class.
		/// </summary>
		/// <remarks>
		/// The instance of the <see cref="AnsiColoredConsoleAppender" /> class is set up to write 
		/// to the standard output stream.
		/// </remarks>
		public AnsiColoredConsoleAppender() 
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AnsiColoredConsoleAppender" /> class
		/// with the specified layout.
		/// </summary>
		/// <param name="layout">the layout to use for this appender</param>
		/// <remarks>
		/// The instance of the <see cref="AnsiColoredConsoleAppender" /> class is set up to write 
		/// to the standard output stream.
		/// </remarks>
		public AnsiColoredConsoleAppender(ILayout layout) : this(layout, false)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AnsiColoredConsoleAppender" /> 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>
		public AnsiColoredConsoleAppender(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>
		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>
		public void AddMapping(AnsiColoredConsoleAppenderLevelColorMapping mapping)
		{
			// ushort usMapping = (ushort)((int)mapping.ForeColor + (((int)mapping.BackColor) << 4) );
			m_Level2ColorMap[mapping.Level] = mapping;
		}

		/// <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>
		public class AnsiColoredConsoleAppenderLevelColorMapping
		{
			private log4net.Core.Level m_level;

			private Attributes m_foreAttributes;
			private Foregrounds m_foreColor;
			private Backgrounds m_backColor;

			/// <summary>
			/// The level to map to a color
			/// </summary>
			public log4net.Core.Level Level
			{
				get { return m_level; }
				set { m_level = value; }
			}

			/// <summary>
			/// The mapped foreground color for the specified level
			/// </summary>
			public Foregrounds ForeColor
			{
				get { return m_foreColor; }
				set { m_foreColor = value; }
			}

			/// <summary>
			/// The mapped background color for the specified level
			/// </summary>
			public Backgrounds BackColor
			{
				get { return m_backColor; }
				set { m_backColor = value; }
			}

			/// <summary>
			/// The mapped attributes for the specified level
			/// </summary>
			public Attributes Attrs
			{
				get { return m_foreAttributes; }
				set { m_foreAttributes = value; }
			}

			/// <summary>
			/// The Ansi color codes for this object
			/// </summary>
			public string AnsiCodes
			{
				get { return "\x1b[" + (int)m_backColor + ";" + (int)m_foreAttributes + ";" + (int)m_foreColor + "m"; }
			}
		}

		#endregion Public Instance Properties

		#region Override implementation of AppenderSkeleton

		/// <summary>
		/// This method is called by the <see cref="AppenderSkeleton.DoAppend"/> 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>
		override protected void Append(log4net.Core.LoggingEvent loggingEvent) 
		{
			string postEventCodes = "\x1b[0m";
			string eventCodes = "";

			// see if there is a lookup.
			AnsiColoredConsoleAppenderLevelColorMapping colLookup =
			  (AnsiColoredConsoleAppenderLevelColorMapping)m_Level2ColorMap[loggingEvent.Level];
			if(colLookup != null)
			{
				eventCodes = colLookup.AnsiCodes;
			}

			string loggingMessage = eventCodes + RenderLoggingEvent(loggingEvent);

			// on most terminals there are weird effects if we don't clear the background color
			// before the new line.  This checks to see if it ends with a newline, and if
			// so, inserts the clear codes before the newline, otherwise the clear codes
			// are inserted afterwards.
			if (loggingMessage[loggingMessage.Length - 1] == '\n') {
				loggingMessage = loggingMessage.Insert(loggingMessage.Length - 1, postEventCodes);
			} else {
				loggingMessage = loggingMessage + postEventCodes;
			}

#if NETCF
			// Write to the output stream
			Console.Write(loggingMessage) )
#else
			if (m_writeToErrorStream)
			{
				// Write to the error stream
				Console.Error.Write(loggingMessage);
			}
			else
			{
				// Write to the output stream
				Console.Write(loggingMessage);
			}
#endif
		
		}

		/// <summary>
		/// This appender requires a <see cref="Layout"/> to be set.
		/// </summary>
		/// <value><c>true</c></value>
		override protected bool RequiresLayout
		{
			get { return true; }
		}

		#endregion Override implementation of AppenderSkeleton

		#region Public Static Fields

		/// <summary>
		/// The <see cref="AnsiColoredConsoleAppender.Target"/> to use when writing to the Console 
		/// standard output stream.
		/// </summary>
		public const string ConsoleOut = "Console.Out";

		/// <summary>
		/// The <see cref="AnsiColoredConsoleAppender.Target"/> to use when writing to the Console 
		/// standard error output stream.
		/// </summary>
		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 colour value
		/// </summary>
		private System.Collections.Hashtable m_Level2ColorMap = new System.Collections.Hashtable();

		#endregion Private Instances Fields
	}
}

Reply via email to