#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.IO; using System.Collections; using log4net.Filter; using log4net.Util; using log4net.Layout; using log4net.Core; namespace log4net.Appender { /// /// Abstract base class implementation of . /// /// /// /// This class provides the code for common functionality, such /// as support for threshold filtering and support for general filters. /// /// /// Appenders can also implement the interface. Therefore /// they would require that the method /// be called after the appenders properties have been configured. /// /// /// Nicko Cadell /// Gert Driesen public abstract class AppenderSkeleton : IAppender, IBulkAppender, IOptionHandler { #region Protected Instance Constructors /// /// Default constructor /// /// /// Empty default constructor /// protected AppenderSkeleton() { m_errorHandler = new OnlyOnceErrorHandler(this.GetType().Name); } #endregion Protected Instance Constructors #region Finalizer /// /// Finalizes this appender by calling the implementation's /// method. /// /// /// /// If this appender has not been closed then the Finalize method /// will call . /// /// ~AppenderSkeleton() { // An appender might be closed then garbage collected. // There is no point in closing twice. if (!m_closed) { LogLog.Debug(declaringType, "Finalizing appender named ["+m_name+"]."); Close(); } } #endregion Finalizer #region Public Instance Properties /// /// Gets or sets the threshold of this appender. /// /// /// The threshold of the appender. /// /// /// /// All log events with lower level than the threshold level are ignored /// by the appender. /// /// /// In configuration files this option is specified by setting the /// value of the option to a level /// string, such as "DEBUG", "INFO" and so on. /// /// public Level Threshold { get { return m_threshold; } set { m_threshold = value; } } /// /// Gets or sets the for this appender. /// /// The of the appender /// /// /// The provides a default /// implementation for the property. /// /// virtual public IErrorHandler ErrorHandler { get { return this.m_errorHandler; } set { lock(this) { if (value == null) { // We do not throw exception here since the cause is probably a // bad config file. LogLog.Warn(declaringType, "You have tried to set a null error-handler."); } else { m_errorHandler = value; } } } } /// /// The filter chain. /// /// The head of the filter chain filter chain. /// /// /// Returns the head Filter. The Filters are organized in a linked list /// and so all Filters on this Appender are available through the result. /// /// virtual public IFilter FilterHead { get { return m_headFilter; } } /// /// Gets or sets the for this appender. /// /// The layout of the appender. /// /// /// See for more information. /// /// /// virtual public ILayout Layout { get { return m_layout; } set { m_layout = value; } } #endregion #region Implementation of IOptionHandler /// /// Initialize the appender based on the options set /// /// /// /// This is part of the delayed object /// activation scheme. The method must /// be called on this object after the configuration properties have /// been set. Until is called this /// object is in an undefined state and must not be used. /// /// /// If any of the configuration properties are modified then /// must be called again. /// /// virtual public void ActivateOptions() { } #endregion Implementation of IOptionHandler #region Implementation of IAppender /// /// Gets or sets the name of this appender. /// /// The name of the appender. /// /// /// The name uniquely identifies the appender. /// /// public string Name { get { return m_name; } set { m_name = value; } } /// /// Closes the appender and release resources. /// /// /// /// Release any resources allocated within the appender such as file handles, /// network connections, etc. /// /// /// It is a programming error to append to a closed appender. /// /// /// This method cannot be overridden by subclasses. This method /// delegates the closing of the appender to the /// method which must be overridden in the subclass. /// /// public void Close() { // This lock prevents the appender being closed while it is still appending lock(this) { if (!m_closed) { OnClose(); m_closed = true; } } } /// /// Performs threshold checks and invokes filters before /// delegating actual logging to the subclasses specific /// method. /// /// The event to log. /// /// /// This method cannot be overridden by derived classes. A /// derived class should override the method /// which is called by this method. /// /// /// The implementation of this method is as follows: /// /// /// /// /// /// Checks that the severity of the /// is greater than or equal to the of this /// appender. /// /// /// /// Checks that the chain accepts the /// . /// /// /// /// /// Calls and checks that /// it returns true. /// /// /// /// /// If all of the above steps succeed then the /// will be passed to the abstract method. /// /// public void DoAppend(LoggingEvent loggingEvent) { // This lock is absolutely critical for correct formatting // of the message in a multi-threaded environment. Without // this, the message may be broken up into elements from // multiple thread contexts (like get the wrong thread ID). lock(this) { if (m_closed) { ErrorHandler.Error("Attempted to append to closed appender named ["+m_name+"]."); return; } // prevent re-entry if (m_recursiveGuard) { return; } try { m_recursiveGuard = true; if (FilterEvent(loggingEvent) && PreAppendCheck()) { this.Append(loggingEvent); } } catch(Exception ex) { ErrorHandler.Error("Failed in DoAppend", ex); } #if !MONO && !NET_2_0 // on .NET 2.0 (and higher) and Mono (all profiles), // exceptions that do not derive from System.Exception will be // wrapped in a RuntimeWrappedException by the runtime, and as // such will be catched by the catch clause above catch { // Catch handler for non System.Exception types ErrorHandler.Error("Failed in DoAppend (unknown exception)"); } #endif finally { m_recursiveGuard = false; } } } #endregion Implementation of IAppender #region Implementation of IBulkAppender /// /// Performs threshold checks and invokes filters before /// delegating actual logging to the subclasses specific /// method. /// /// The array of events to log. /// /// /// This method cannot be overridden by derived classes. A /// derived class should override the method /// which is called by this method. /// /// /// The implementation of this method is as follows: /// /// /// /// /// /// Checks that the severity of the /// is greater than or equal to the of this /// appender. /// /// /// /// Checks that the chain accepts the /// . /// /// /// /// /// Calls and checks that /// it returns true. /// /// /// /// /// If all of the above steps succeed then the /// will be passed to the method. /// /// public void DoAppend(LoggingEvent[] loggingEvents) { // This lock is absolutely critical for correct formatting // of the message in a multi-threaded environment. Without // this, the message may be broken up into elements from // multiple thread contexts (like get the wrong thread ID). lock(this) { if (m_closed) { ErrorHandler.Error("Attempted to append to closed appender named ["+m_name+"]."); return; } // prevent re-entry if (m_recursiveGuard) { return; } try { m_recursiveGuard = true; ArrayList filteredEvents = new ArrayList(loggingEvents.Length); foreach(LoggingEvent loggingEvent in loggingEvents) { if (FilterEvent(loggingEvent)) { filteredEvents.Add(loggingEvent); } } if (filteredEvents.Count > 0 && PreAppendCheck()) { this.Append((LoggingEvent[])filteredEvents.ToArray(typeof(LoggingEvent))); } } catch(Exception ex) { ErrorHandler.Error("Failed in Bulk DoAppend", ex); } #if !MONO && !NET_2_0 // on .NET 2.0 (and higher) and Mono (all profiles), // exceptions that do not derive from System.Exception will be // wrapped in a RuntimeWrappedException by the runtime, and as // such will be catched by the catch clause above catch { // Catch handler for non System.Exception types ErrorHandler.Error("Failed in Bulk DoAppend (unknown exception)"); } #endif finally { m_recursiveGuard = false; } } } #endregion Implementation of IBulkAppender /// /// Test if the logging event should we output by this appender /// /// the event to test /// true if the event should be output, false if the event should be ignored /// /// /// This method checks the logging event against the threshold level set /// on this appender and also against the filters specified on this /// appender. /// /// /// The implementation of this method is as follows: /// /// /// /// /// /// Checks that the severity of the /// is greater than or equal to the of this /// appender. /// /// /// /// Checks that the chain accepts the /// . /// /// /// /// /// virtual protected bool FilterEvent(LoggingEvent loggingEvent) { if (!IsAsSevereAsThreshold(loggingEvent.Level)) { return false; } IFilter f = this.FilterHead; while(f != null) { switch(f.Decide(loggingEvent)) { case FilterDecision.Deny: return false; // Return without appending case FilterDecision.Accept: f = null; // Break out of the loop break; case FilterDecision.Neutral: f = f.Next; // Move to next filter break; } } return true; } #region Public Instance Methods /// /// Adds a filter to the end of the filter chain. /// /// the filter to add to this appender /// /// /// The Filters are organized in a linked list. /// /// /// Setting this property causes the new filter to be pushed onto the /// back of the filter chain. /// /// virtual public void AddFilter(IFilter filter) { if (filter == null) { throw new ArgumentNullException("filter param must not be null"); } if (m_headFilter == null) { m_headFilter = m_tailFilter = filter; } else { m_tailFilter.Next = filter; m_tailFilter = filter; } } /// /// Clears the filter list for this appender. /// /// /// /// Clears the filter list for this appender. /// /// virtual public void ClearFilters() { m_headFilter = m_tailFilter = null; } #endregion Public Instance Methods #region Protected Instance Methods /// /// Checks if the message level is below this appender's threshold. /// /// to test against. /// /// /// If there is no threshold set, then the return value is always true. /// /// /// /// true if the meets the /// requirements of this appender. /// virtual protected bool IsAsSevereAsThreshold(Level level) { return ((m_threshold == null) || level >= m_threshold); } /// /// Is called when the appender is closed. Derived classes should override /// this method if resources need to be released. /// /// /// /// Releases any resources allocated within the appender such as file handles, /// network connections, etc. /// /// /// It is a programming error to append to a closed appender. /// /// virtual protected void OnClose() { // Do nothing by default } /// /// Subclasses of should implement this method /// to perform actual logging. /// /// The event to append. /// /// /// A subclass must implement this method to perform /// logging of the . /// /// This method will be called by /// if all the conditions listed for that method are met. /// /// /// To restrict the logging of events in the appender /// override the method. /// /// abstract protected void Append(LoggingEvent loggingEvent); /// /// Append a bulk array of logging events. /// /// the array of logging events /// /// /// This base class implementation calls the /// method for each element in the bulk array. /// /// /// A sub class that can better process a bulk array of events should /// override this method in addition to . /// /// virtual protected void Append(LoggingEvent[] loggingEvents) { foreach(LoggingEvent loggingEvent in loggingEvents) { Append(loggingEvent); } } /// /// Called before as a precondition. /// /// /// /// This method is called by /// before the call to the abstract method. /// /// /// This method can be overridden in a subclass to extend the checks /// made before the event is passed to the method. /// /// /// A subclass should ensure that they delegate this call to /// this base class if it is overridden. /// /// /// true if the call to should proceed. virtual protected bool PreAppendCheck() { if ((m_layout == null) && RequiresLayout) { ErrorHandler.Error("AppenderSkeleton: No layout set for the appender named ["+m_name+"]."); return false; } return true; } /// /// Renders the to a string. /// /// The event to render. /// The event rendered as a string. /// /// /// Helper method to render a to /// a string. This appender must have a /// set to render the to /// a string. /// /// If there is exception data in the logging event and /// the layout does not process the exception, this method /// will append the exception text to the rendered string. /// /// /// Where possible use the alternative version of this method /// . /// That method streams the rendering onto an existing Writer /// which can give better performance if the caller already has /// a open and ready for writing. /// /// protected string RenderLoggingEvent(LoggingEvent loggingEvent) { // Create the render writer on first use if (m_renderWriter == null) { m_renderWriter = new ReusableStringWriter(System.Globalization.CultureInfo.InvariantCulture); } lock (m_renderWriter) { // Reset the writer so we can reuse it m_renderWriter.Reset(c_renderBufferMaxCapacity, c_renderBufferSize); RenderLoggingEvent(m_renderWriter, loggingEvent); return m_renderWriter.ToString(); } } /// /// Renders the to a string. /// /// The event to render. /// The TextWriter to write the formatted event to /// /// /// Helper method to render a to /// a string. This appender must have a /// set to render the to /// a string. /// /// If there is exception data in the logging event and /// the layout does not process the exception, this method /// will append the exception text to the rendered string. /// /// /// Use this method in preference to /// where possible. If, however, the caller needs to render the event /// to a string then does /// provide an efficient mechanism for doing so. /// /// protected void RenderLoggingEvent(TextWriter writer, LoggingEvent loggingEvent) { if (m_layout == null) { throw new InvalidOperationException("A layout must be set"); } if (m_layout.IgnoresException) { string exceptionStr = loggingEvent.GetExceptionString(); if (exceptionStr != null && exceptionStr.Length > 0) { // render the event and the exception m_layout.Format(writer, loggingEvent); writer.WriteLine(exceptionStr); } else { // there is no exception to render m_layout.Format(writer, loggingEvent); } } else { // The layout will render the exception m_layout.Format(writer, loggingEvent); } } /// /// Tests if this appender requires a to be set. /// /// /// /// In the rather exceptional case, where the appender /// implementation admits a layout but can also work without it, /// then the appender should return true. /// /// /// This default implementation always returns false. /// /// /// /// true if the appender requires a layout object, otherwise false. /// virtual protected bool RequiresLayout { get { return false; } } #endregion #region Private Instance Fields /// /// The layout of this appender. /// /// /// See for more information. /// private ILayout m_layout; /// /// The name of this appender. /// /// /// See for more information. /// private string m_name; /// /// The level threshold of this appender. /// /// /// /// There is no level threshold filtering by default. /// /// /// See for more information. /// /// private Level m_threshold; /// /// It is assumed and enforced that errorHandler is never null. /// /// /// /// It is assumed and enforced that errorHandler is never null. /// /// /// See for more information. /// /// private IErrorHandler m_errorHandler; /// /// The first filter in the filter chain. /// /// /// /// Set to null initially. /// /// /// See for more information. /// /// private IFilter m_headFilter; /// /// The last filter in the filter chain. /// /// /// See for more information. /// private IFilter m_tailFilter; /// /// Flag indicating if this appender is closed. /// /// /// See for more information. /// private bool m_closed = false; /// /// The guard prevents an appender from repeatedly calling its own DoAppend method /// private bool m_recursiveGuard = false; /// /// StringWriter used to render events /// private ReusableStringWriter m_renderWriter = null; #endregion Private Instance Fields #region Constants /// /// Initial buffer size /// private const int c_renderBufferSize = 256; /// /// Maximum buffer size before it is recycled /// private const int c_renderBufferMaxCapacity = 1024; #endregion #region Private Static Fields /// /// The fully qualified type of the AppenderSkeleton class. /// /// /// Used by the internal logger to record the Type of the /// log message. /// private readonly static Type declaringType = typeof(AppenderSkeleton); #endregion Private Static Fields } }