blob: 474bac42c08dff3ed5a67c3a44500e29cdc8ccd5 [file] [log] [blame]
#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.Generic;
using log4net.Filter;
using log4net.Util;
using log4net.Layout;
using log4net.Core;
namespace log4net.Appender;
/// <summary>
/// Abstract base class implementation of <see cref="IAppender"/>.
/// </summary>
/// <remarks>
/// <para>
/// This class provides the code for common functionality, such
/// as support for threshold filtering and support for general filters.
/// </para>
/// <para>
/// Appenders can also implement the <see cref="IOptionHandler"/> interface. Therefore
/// they would require that the <see cref="IOptionHandler.ActivateOptions()"/> method
/// be called after the appenders properties have been configured.
/// </para>
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
public abstract class AppenderSkeleton : IAppender, IBulkAppender, IOptionHandler, IFlushable
{
/// <summary>
/// Default constructor
/// </summary>
/// <remarks>
/// <para>Empty default constructor</para>
/// </remarks>
protected AppenderSkeleton() => _errorHandler = new OnlyOnceErrorHandler(GetType().Name);
/// <summary>
/// Finalizes this appender by calling the implementation's
/// <see cref="Close"/> method.
/// </summary>
/// <remarks>
/// <para>
/// If this appender has not been closed then the <c>Finalize</c> method
/// will call <see cref="Close"/>.
/// </para>
/// </remarks>
~AppenderSkeleton()
{
// An appender might be closed then garbage collected.
// There is no point in closing twice.
if (!_isClosed)
{
LogLog.Debug(_declaringType, $"Finalizing appender named [{Name}].");
Close();
}
}
/// <summary>
/// Gets or sets the threshold <see cref="Level"/> of this appender.
/// Defaults to <see cref="Level.All"/>.
/// </summary>
/// <value>
/// The threshold <see cref="Level"/> of the appender.
/// </value>
/// <remarks>
/// <para>
/// All log events with lower level than the threshold level are ignored
/// by the appender.
/// </para>
/// <para>
/// In configuration files this option is specified by setting the
/// value of the <see cref="Threshold"/> option to a level
/// string, such as "DEBUG", "INFO" and so on.
/// </para>
/// </remarks>
public Level Threshold { get; set; } = Level.All;
/// <summary>
/// Gets or sets the <see cref="IErrorHandler"/> for this appender.
/// </summary>
/// <value>The <see cref="IErrorHandler"/> of the appender</value>
/// <remarks>
/// <para>
/// The <see cref="AppenderSkeleton"/> provides a default
/// implementation for the <see cref="ErrorHandler"/> property.
/// </para>
/// </remarks>
public virtual IErrorHandler ErrorHandler
{
get => _errorHandler;
set
{
lock (LockObj)
{
if (value is 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
{
_errorHandler = value;
}
}
}
}
/// <summary>
/// The filter chain.
/// </summary>
/// <value>The head of the filter chain.</value>
/// <remarks>
/// <para>
/// Returns the head Filter. The Filters are organized in a linked list
/// and so all Filters on this Appender are available through the result.
/// </para>
/// </remarks>
public virtual IFilter? FilterHead { get; private set; }
/// <summary>
/// Gets or sets the <see cref="ILayout"/> for this appender.
/// </summary>
/// <value>The layout of the appender.</value>
/// <remarks>
/// <para>
/// See <see cref="RequiresLayout"/> for more information.
/// </para>
/// </remarks>
/// <seealso cref="RequiresLayout"/>
public virtual ILayout? Layout { get; set; }
/// <summary>
/// Initialize the appender based on the options set
/// </summary>
/// <remarks>
/// <para>
/// This is part of the <see cref="IOptionHandler"/> delayed object
/// activation scheme. The <see cref="ActivateOptions"/> method must
/// be called on this object after the configuration properties have
/// been set. Until <see cref="ActivateOptions"/> is called this
/// object is in an undefined state and must not be used.
/// </para>
/// <para>
/// If any of the configuration properties are modified then
/// <see cref="ActivateOptions"/> must be called again.
/// </para>
/// </remarks>
public virtual void ActivateOptions()
{
}
/// <summary>
/// Gets or sets the name that uniquely identifies this appender.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Closes the appender and releases resources.
/// </summary>
/// <remarks>
/// <para>
/// Release any resources allocated within the appender such as file handles,
/// network connections, etc.
/// </para>
/// <para>
/// It is a programming error to append to a closed appender.
/// </para>
/// <para>
/// This method cannot be overridden by subclasses. This method
/// delegates the closing of the appender to the <see cref="OnClose"/>
/// method which must be overridden in the subclass.
/// </para>
/// </remarks>
public void Close()
{
// This lock prevents the appender being closed while it is still appending
lock (LockObj)
{
if (!_isClosed)
{
OnClose();
_isClosed = true;
}
}
}
/// <summary>
/// Performs threshold checks and invokes filters before
/// delegating actual logging to the subclasses specific
/// <see cref="Append(LoggingEvent)"/> method.
/// </summary>
/// <param name="loggingEvent">The event to log.</param>
/// <remarks>
/// <para>
/// This method cannot be overridden by derived classes. A
/// derived class should override the <see cref="Append(LoggingEvent)"/> method
/// which is called by this method.
/// </para>
/// <para>
/// The implementation of this method is as follows:
/// </para>
/// <para>
/// <list type="bullet">
/// <item>
/// <description>
/// Checks that the severity of the <paramref name="loggingEvent"/>
/// is greater than or equal to the <see cref="Threshold"/> of this
/// appender.</description>
/// </item>
/// <item>
/// <description>
/// Checks that the <see cref="IFilter"/> chain accepts the
/// <paramref name="loggingEvent"/>.
/// </description>
/// </item>
/// <item>
/// <description>
/// Calls <see cref="PreAppendCheck()"/> and checks that
/// it returns <c>true</c>.</description>
/// </item>
/// </list>
/// </para>
/// <para>
/// If all of the above steps succeed then the <paramref name="loggingEvent"/>
/// will be passed to the abstract <see cref="Append(LoggingEvent)"/> method.
/// </para>
/// </remarks>
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 (LockObj)
{
if (_isClosed)
{
ErrorHandler.Error($"Attempted to append to closed appender named [{Name}].");
return;
}
// prevent re-entry
if (_recursiveGuard)
{
return;
}
try
{
_recursiveGuard = true;
if (FilterEvent(loggingEvent) && PreAppendCheck())
{
Append(loggingEvent);
}
}
catch (Exception ex) when (!ex.IsFatal())
{
ErrorHandler.Error("Failed in DoAppend", ex);
}
finally
{
_recursiveGuard = false;
}
}
}
/// <summary>
/// Performs threshold checks and invokes filters before
/// delegating actual logging to the subclasses specific
/// <see cref="Append(LoggingEvent[])"/> method.
/// </summary>
/// <param name="loggingEvents">The array of events to log.</param>
/// <remarks>
/// <para>
/// This method cannot be overridden by derived classes. A
/// derived class should override the <see cref="Append(LoggingEvent[])"/> method
/// which is called by this method.
/// </para>
/// <para>
/// The implementation of this method is as follows:
/// </para>
/// <para>
/// <list type="bullet">
/// <item>
/// <description>
/// Checks that the severity of the <paramref name="loggingEvents"/>
/// is greater than or equal to the <see cref="Threshold"/> of this
/// appender.</description>
/// </item>
/// <item>
/// <description>
/// Checks that the <see cref="IFilter"/> chain accepts the
/// <paramref name="loggingEvents"/>.
/// </description>
/// </item>
/// <item>
/// <description>
/// Calls <see cref="PreAppendCheck()"/> and checks that
/// it returns <c>true</c>.</description>
/// </item>
/// </list>
/// </para>
/// <para>
/// If all of the above steps succeed then the <paramref name="loggingEvents"/>
/// will be passed to the <see cref="Append(LoggingEvent[])"/> method.
/// </para>
/// </remarks>
public void DoAppend(LoggingEvent[] loggingEvents)
{
loggingEvents.EnsureNotNull();
// 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 (LockObj)
{
if (_isClosed)
{
ErrorHandler.Error("Attempted to append to closed appender named [" + Name + "].");
return;
}
// prevent re-entry
if (_recursiveGuard)
{
return;
}
try
{
_recursiveGuard = true;
List<LoggingEvent> filteredEvents = new(loggingEvents.Length);
foreach (LoggingEvent loggingEvent in loggingEvents)
{
if (FilterEvent(loggingEvent))
{
filteredEvents.Add(loggingEvent);
}
}
if (filteredEvents.Count > 0 && PreAppendCheck())
{
Append(filteredEvents);
}
}
catch (Exception e) when (!e.IsFatal())
{
ErrorHandler.Error("Failed in Bulk DoAppend", e);
}
finally
{
_recursiveGuard = false;
}
}
}
/// <summary>
/// Test if the logging event should we output by this appender
/// </summary>
/// <param name="loggingEvent">the event to test</param>
/// <returns><c>true</c> if the event should be output, <c>false</c> if the event should be ignored</returns>
/// <remarks>
/// <para>
/// This method checks the logging event against the threshold level set
/// on this appender and also against the filters specified on this
/// appender.
/// </para>
/// <para>
/// The implementation of this method is as follows:
/// </para>
/// <para>
/// <list type="bullet">
/// <item>
/// <description>
/// Checks that the severity of the <paramref name="loggingEvent"/>
/// is greater than or equal to the <see cref="Threshold"/> of this
/// appender.</description>
/// </item>
/// <item>
/// <description>
/// Checks that the <see cref="IFilter"/> chain accepts the
/// <paramref name="loggingEvent"/>.
/// </description>
/// </item>
/// </list>
/// </para>
/// </remarks>
protected virtual bool FilterEvent(LoggingEvent loggingEvent)
{
loggingEvent.EnsureNotNull();
if (!IsAsSevereAsThreshold(loggingEvent.Level))
{
return false;
}
IFilter? f = FilterHead;
while (f is not 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;
}
/// <summary>
/// Adds a filter to the end of the filter chain.
/// </summary>
/// <param name="filter">the filter to add to this appender</param>
/// <remarks>
/// <para>
/// The Filters are organized in a linked list.
/// </para>
/// <para>
/// Setting this property causes the new filter to be pushed onto the
/// back of the filter chain.
/// </para>
/// </remarks>
public virtual void AddFilter(IFilter filter)
{
filter.EnsureNotNull();
if (FilterHead is null)
{
FilterHead = _tailFilter = filter;
}
else
{
_tailFilter!.Next = filter;
_tailFilter = filter;
}
}
/// <summary>
/// Clears the filter list for this appender.
/// </summary>
/// <remarks>
/// <para>
/// Clears the filter list for this appender.
/// </para>
/// </remarks>
public virtual void ClearFilters() => FilterHead = _tailFilter = null;
/// <summary>
/// Checks if the message level is below this appender's threshold.
/// </summary>
/// <param name="level"><see cref="Level"/> to test against.</param>
/// <returns>
/// <c>true</c> if the <paramref name="level"/> meets the <see cref="Threshold"/>
/// requirements of this appender. A null level always maps to <c>true</c>,
/// the equivalent of <see cref="Level.All"/>.
/// </returns>
protected virtual bool IsAsSevereAsThreshold(Level? level) => level is null || level >= Threshold;
/// <summary>
/// Is called when the appender is closed. Derived classes should override
/// this method if resources need to be released.
/// </summary>
/// <remarks>
/// <para>
/// Releases any resources allocated within the appender such as file handles,
/// network connections, etc.
/// </para>
/// <para>
/// It is a programming error to append to a closed appender.
/// </para>
/// </remarks>
protected virtual void OnClose()
{
// Do nothing by default
}
/// <summary>
/// Subclasses of <see cref="AppenderSkeleton"/> should implement this method
/// to perform actual logging.
/// </summary>
/// <param name="loggingEvent">The event to append.</param>
/// <remarks>
/// <para>
/// A subclass must implement this method to perform
/// logging of the <paramref name="loggingEvent"/>.
/// </para>
/// <para>This method will be called by <see cref="DoAppend(LoggingEvent)"/>
/// if all the conditions listed for that method are met.
/// </para>
/// <para>
/// To restrict the logging of events in the appender
/// override the <see cref="PreAppendCheck()"/> method.
/// </para>
/// </remarks>
protected abstract void Append(LoggingEvent loggingEvent);
/// <summary>
/// Append a bulk array of logging events.
/// </summary>
/// <param name="loggingEvents">the array of logging events</param>
/// <remarks>
/// <para>
/// This base class implementation calls the <see cref="Append(LoggingEvent)"/>
/// method for each element in the bulk array.
/// </para>
/// <para>
/// A subclass that can better process a bulk array of events should
/// override this method in addition to <see cref="Append(LoggingEvent)"/>.
/// </para>
/// </remarks>
protected virtual void Append(LoggingEvent[] loggingEvents)
{
foreach (LoggingEvent loggingEvent in loggingEvents.EnsureNotNull())
{
Append(loggingEvent);
}
}
/// <summary>
/// Appends logging events.
/// </summary>
/// <param name="loggingEvents">The logging events</param>
/// <remarks>
/// <para>
/// This base class implementation calls the <see cref="Append(LoggingEvent)"/>
/// method for each element in the bulk array.
/// </para>
/// <para>
/// A subclass that can better process a bulk array of events should
/// override this method in addition to <see cref="Append(LoggingEvent)"/>.
/// </para>
/// </remarks>
protected virtual void Append(IEnumerable<LoggingEvent> loggingEvents)
{
foreach (LoggingEvent loggingEvent in loggingEvents.EnsureNotNull())
{
Append(loggingEvent);
}
}
/// <summary>
/// Called before <see cref="Append(LoggingEvent)"/> as a precondition.
/// </summary>
/// <remarks>
/// <para>
/// This method is called by <see cref="DoAppend(LoggingEvent)"/>
/// before the call to the abstract <see cref="Append(LoggingEvent)"/> method.
/// </para>
/// <para>
/// This method can be overridden in a subclass to extend the checks
/// made before the event is passed to the <see cref="Append(LoggingEvent)"/> method.
/// </para>
/// <para>
/// A subclass should ensure that they delegate this call to
/// this base class if it is overridden.
/// </para>
/// </remarks>
/// <returns><c>true</c> if the call to <see cref="Append(LoggingEvent)"/> should proceed.</returns>
protected virtual bool PreAppendCheck()
{
if ((Layout is null) && RequiresLayout)
{
ErrorHandler.Error($"AppenderSkeleton: No layout set for the appender named [{Name}].");
return false;
}
return true;
}
/// <summary>
/// Renders the <see cref="LoggingEvent"/> to a string.
/// </summary>
/// <param name="loggingEvent">The event to render.</param>
/// <returns>The event rendered as a string.</returns>
/// <remarks>
/// <para>
/// Helper method to render a <see cref="LoggingEvent"/> to
/// a string. This appender must have a <see cref="Layout"/>
/// set to render the <paramref name="loggingEvent"/> to
/// a string.
/// </para>
/// <para>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.
/// </para>
/// <para>
/// Where possible use the alternative version of this method
/// <see cref="RenderLoggingEvent(TextWriter,LoggingEvent)"/>.
/// That method streams the rendering onto an existing Writer
/// which can give better performance if the caller already has
/// a <see cref="TextWriter"/> open and ready for writing.
/// </para>
/// </remarks>
protected string RenderLoggingEvent(LoggingEvent loggingEvent)
{
lock (LockObj)
{
// Create the render writer on first use
_renderWriter ??= new(System.Globalization.CultureInfo.InvariantCulture);
// Reset the writer so we can reuse it
_renderWriter.Reset(RenderBufferMaxCapacity, RenderBufferSize);
RenderLoggingEvent(_renderWriter, loggingEvent);
return _renderWriter.ToString();
}
}
/// <summary>
/// Renders the <see cref="LoggingEvent"/> to a string.
/// </summary>
/// <param name="loggingEvent">The event to render.</param>
/// <param name="writer">The TextWriter to write the formatted event to</param>
/// <remarks>
/// <para>
/// Helper method to render a <see cref="LoggingEvent"/> to
/// a string. This appender must have a <see cref="Layout"/>
/// set to render the <paramref name="loggingEvent"/> to
/// a string.
/// </para>
/// <para>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.
/// </para>
/// <para>
/// Use this method in preference to <see cref="RenderLoggingEvent(LoggingEvent)"/>
/// where possible. If, however, the caller needs to render the event
/// to a string then <see cref="RenderLoggingEvent(LoggingEvent)"/> does
/// provide an efficient mechanism for doing so.
/// </para>
/// </remarks>
protected void RenderLoggingEvent(TextWriter writer, LoggingEvent loggingEvent)
{
writer.EnsureNotNull();
if (Layout is null)
{
throw new InvalidOperationException("A layout must be set");
}
if (Layout.IgnoresException)
{
string? exceptionStr = loggingEvent.EnsureNotNull().GetExceptionString();
if (!string.IsNullOrEmpty(exceptionStr))
{
// render the event and the exception
Layout.Format(writer, loggingEvent);
writer.WriteLine(exceptionStr);
}
else
{
// there is no exception to render
Layout.Format(writer, loggingEvent);
}
}
else
{
// The layout will render the exception
Layout.Format(writer, loggingEvent);
}
}
/// <summary>
/// Tests if this appender requires a <see cref="Layout"/> to be set.
/// </summary>
/// <remarks>
/// <para>
/// In the rather exceptional case, where the appender
/// implementation admits a layout but can also work without it,
/// then the appender should return <c>true</c>.
/// </para>
/// <para>
/// This default implementation always returns <c>false</c>.
/// </para>
/// </remarks>
/// <returns>
/// <c>true</c> if the appender requires a layout object, otherwise <c>false</c>.
/// </returns>
protected virtual bool RequiresLayout => false;
/// <summary>
/// Flushes any buffered log data.
/// </summary>
/// <remarks>
/// This implementation doesn't flush anything and always returns true
/// </remarks>
/// <returns><c>True</c> if all logging events were flushed successfully, else <c>false</c>.</returns>
public virtual bool Flush(int millisecondsTimeout) => true;
/// <summary>
/// It is assumed and enforced that errorHandler is never null.
/// </summary>
/// <remarks>
/// <para>
/// See <see cref="ErrorHandler"/> for more information.
/// </para>
/// </remarks>
private IErrorHandler _errorHandler;
/// <summary>
/// The last filter in the filter chain.
/// </summary>
/// <remarks>
/// See <see cref="IFilter"/> for more information.
/// </remarks>
private IFilter? _tailFilter;
/// <summary>
/// Flag indicating if this appender is closed.
/// </summary>
/// <remarks>
/// See <see cref="Close"/> for more information.
/// </remarks>
private bool _isClosed;
/// <summary>
/// The guard prevents an appender from repeatedly calling its own DoAppend method
/// </summary>
private bool _recursiveGuard;
/// <summary>
/// Used for locking actions by this appender.
/// </summary>
protected object LockObj { get; } = new();
/// <summary>
/// StringWriter used to render events
/// </summary>
private ReusableStringWriter? _renderWriter;
/// <summary>
/// Initial buffer size
/// </summary>
private const int RenderBufferSize = 256;
/// <summary>
/// Maximum buffer size before it is recycled
/// </summary>
private const int RenderBufferMaxCapacity = 1024;
/// <summary>
/// The fully qualified type of the AppenderSkeleton class.
/// </summary>
/// <remarks>
/// Used by the internal logger to record the Type of the
/// log message.
/// </remarks>
private static readonly Type _declaringType = typeof(AppenderSkeleton);
}