#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". /// /// virtual public 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. /// /// override protected 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. /// /// override protected 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 } }