#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
using System;
using System.Text;
using System.Globalization;
using log4net.Core;
using log4net.Layout;
using log4net.Util;
namespace log4net.Appender
{
///
/// Appends logging events to the terminal using ANSI color escape sequences.
///
///
///
/// AnsiColorTerminalAppender 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 level of message to be set.
///
///
/// This appender expects the terminal to understand the VT100 control set
/// in order to interpret the color codes. If the terminal or console does not
/// understand the control codes the behavior is not defined.
///
///
/// By default, all output is written to the console's standard output stream.
/// The property can be set to direct the output to the
/// error stream.
///
///
/// NOTE: This appender writes each message to the System.Console.Out or
/// System.Console.Error that is set at the time the event is appended.
/// Therefore it is possible to programmatically redirect the output of this appender
/// (for example NUnit does this to capture program output). While this is the desired
/// behavior of this appender it may have security implications in your application.
///
///
/// When configuring the ANSI colored terminal appender, a mapping should be
/// specified to map a logging level to a color. For example:
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// The Level is the standard log4net logging level and ForeColor and BackColor can be any
/// of the following values:
///
/// - Blue
/// - Green
/// - Red
/// - White
/// - Yellow
/// - Purple
/// - Cyan
///
/// These color values cannot be combined together to make new colors.
///
///
/// The attributes can be any combination of the following:
///
/// - Brightforeground is brighter
/// - Dimforeground is dimmer
/// - Underscoremessage is underlined
/// - Blinkforeground is blinking (does not work on all terminals)
/// - Reverseforeground and background are reversed
/// - Hiddenoutput is hidden
/// - Strikethroughmessage has a line through it
///
/// While any of these attributes may be combined together not all combinations
/// work well together, for example setting both Bright and Dim attributes makes
/// no sense.
///
///
/// Patrick Wagstrom
/// Nicko Cadell
public class AnsiColorTerminalAppender : AppenderSkeleton
{
#region Colors Enum
///
/// The enum of possible display attributes
///
///
///
/// The following flags can be combined together to
/// form the ANSI color attributes.
///
///
///
[Flags]
public enum AnsiAttributes : int
{
///
/// text is bright
///
Bright = 1,
///
/// text is dim
///
Dim = 2,
///
/// text is underlined
///
Underscore = 4,
///
/// text is blinking
///
///
/// Not all terminals support this attribute
///
Blink = 8,
///
/// text and background colors are reversed
///
Reverse = 16,
///
/// text is hidden
///
Hidden = 32,
///
/// text is displayed with a strikethrough
///
Strikethrough = 64,
///
/// text color is light
///
Light = 128
}
///
/// The enum of possible foreground or background color values for
/// use with the color mapping method
///
///
///
/// The output can be in one for the following ANSI colors.
///
///
///
public enum AnsiColor : int
{
///
/// color is black
///
Black = 0,
///
/// color is red
///
Red = 1,
///
/// color is green
///
Green = 2,
///
/// color is yellow
///
Yellow = 3,
///
/// color is blue
///
Blue = 4,
///
/// color is magenta
///
Magenta = 5,
///
/// color is cyan
///
Cyan = 6,
///
/// color is white
///
White = 7
}
#endregion
#region Public Instance Constructors
///
/// Initializes a new instance of the class.
///
///
/// The instance of the class is set up to write
/// to the standard output stream.
///
public AnsiColorTerminalAppender()
{
}
#endregion Public Instance Constructors
#region Public Instance Properties
///
/// Target is the value of the console output stream.
///
///
/// Target is the value of the console output stream.
/// This is either "Console.Out" or "Console.Error".
///
///
///
/// Target is the value of the console output stream.
/// This is either "Console.Out" or "Console.Error".
///
///
public virtual string Target
{
get { return m_writeToErrorStream ? ConsoleError : ConsoleOut; }
set
{
string trimmedTargetName = value.Trim();
if (SystemInfo.EqualsIgnoringCase(ConsoleError, trimmedTargetName))
{
m_writeToErrorStream = true;
}
else
{
m_writeToErrorStream = false;
}
}
}
///
/// Add a mapping of level to color
///
/// The mapping to add
///
///
/// Add a mapping to this appender.
/// Each mapping defines the foreground and background colours
/// for a level.
///
///
public void AddMapping(LevelColors mapping)
{
m_levelMapping.Add(mapping);
}
#endregion Public Instance Properties
#region Override implementation of AppenderSkeleton
///
/// This method is called by the method.
///
/// The event to log.
///
///
/// Writes the event to the console.
///
///
/// The format of the output will depend on the appender's layout.
///
///
protected override void Append(log4net.Core.LoggingEvent loggingEvent)
{
string loggingMessage = RenderLoggingEvent(loggingEvent);
// see if there is a specified lookup.
LevelColors levelColors = m_levelMapping.Lookup(loggingEvent.Level) as LevelColors;
if (levelColors != null)
{
// Prepend the Ansi Color code
loggingMessage = levelColors.CombinedColor + loggingMessage;
}
// 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.Length > 1)
{
if (loggingMessage.EndsWith("\r\n") || loggingMessage.EndsWith("\n\r"))
{
loggingMessage = loggingMessage.Insert(loggingMessage.Length - 2, PostEventCodes);
}
else if (loggingMessage.EndsWith("\n") || loggingMessage.EndsWith("\r"))
{
loggingMessage = loggingMessage.Insert(loggingMessage.Length - 1, PostEventCodes);
}
else
{
loggingMessage = loggingMessage + PostEventCodes;
}
}
else
{
if (loggingMessage[0] == '\n' || loggingMessage[0] == '\r')
{
loggingMessage = PostEventCodes + loggingMessage;
}
else
{
loggingMessage = loggingMessage + PostEventCodes;
}
}
#if NETCF_1_0
// 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
}
///
/// This appender requires a to be set.
///
/// true
///
///
/// This appender requires a to be set.
///
///
protected override bool RequiresLayout
{
get { return true; }
}
///
/// Initialize the options for this appender
///
///
///
/// Initialize the level to color mappings set on this appender.
///
///
public override void ActivateOptions()
{
base.ActivateOptions();
m_levelMapping.ActivateOptions();
}
#endregion Override implementation of AppenderSkeleton
#region Public Static Fields
///
/// The to use when writing to the Console
/// standard output stream.
///
///
///
/// The to use when writing to the Console
/// standard output stream.
///
///
public const string ConsoleOut = "Console.Out";
///
/// The to use when writing to the Console
/// standard error output stream.
///
///
///
/// The to use when writing to the Console
/// standard error output stream.
///
///
public const string ConsoleError = "Console.Error";
#endregion Public Static Fields
#region Private Instances Fields
///
/// Flag to write output to the error stream rather than the standard output stream
///
private bool m_writeToErrorStream = false;
///
/// Mapping from level object to color value
///
private LevelMapping m_levelMapping = new LevelMapping();
///
/// Ansi code to reset terminal
///
private const string PostEventCodes = "\x1b[0m";
#endregion Private Instances Fields
#region LevelColors LevelMapping Entry
///
/// 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.
///
///
///
/// Defines the mapping between a level and the color it should be displayed in.
///
///
public class LevelColors : LevelMappingEntry
{
private AnsiColor m_foreColor;
private AnsiColor m_backColor;
private AnsiAttributes m_attributes;
private string m_combinedColor = "";
///
/// The mapped foreground color for the specified level
///
///
///
/// Required property.
/// The mapped foreground color for the specified level
///
///
public AnsiColor ForeColor
{
get { return m_foreColor; }
set { m_foreColor = value; }
}
///
/// The mapped background color for the specified level
///
///
///
/// Required property.
/// The mapped background color for the specified level
///
///
public AnsiColor BackColor
{
get { return m_backColor; }
set { m_backColor = value; }
}
///
/// The color attributes for the specified level
///
///
///
/// Required property.
/// The color attributes for the specified level
///
///
public AnsiAttributes Attributes
{
get { return m_attributes; }
set { m_attributes = value; }
}
///
/// Initialize the options for the object
///
///
///
/// Combine the and together
/// and append the attributes.
///
///
public override void ActivateOptions()
{
base.ActivateOptions();
StringBuilder buf = new StringBuilder();
// Reset any existing codes
buf.Append("\x1b[0;");
int lightAdjustment = ((m_attributes & AnsiAttributes.Light) > 0) ? 60 : 0;
// set the foreground color
buf.Append(30 + lightAdjustment + (int)m_foreColor);
buf.Append(';');
// set the background color
buf.Append(40 + lightAdjustment + (int)m_backColor);
// set the attributes
if ((m_attributes & AnsiAttributes.Bright) > 0)
{
buf.Append(";1");
}
if ((m_attributes & AnsiAttributes.Dim) > 0)
{
buf.Append(";2");
}
if ((m_attributes & AnsiAttributes.Underscore) > 0)
{
buf.Append(";4");
}
if ((m_attributes & AnsiAttributes.Blink) > 0)
{
buf.Append(";5");
}
if ((m_attributes & AnsiAttributes.Reverse) > 0)
{
buf.Append(";7");
}
if ((m_attributes & AnsiAttributes.Hidden) > 0)
{
buf.Append(";8");
}
if ((m_attributes & AnsiAttributes.Strikethrough) > 0)
{
buf.Append(";9");
}
buf.Append('m');
m_combinedColor = buf.ToString();
}
///
/// The combined , and
/// suitable for setting the ansi terminal color.
///
internal string CombinedColor
{
get { return m_combinedColor; }
}
}
#endregion // LevelColors LevelMapping Entry
}
}