| #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 log4net.Util; |
| using log4net.Layout; |
| using log4net.Core; |
| |
| namespace log4net.Appender; |
| |
| /// <summary> |
| /// Sends logging events to a <see cref="TextWriter"/>. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// An Appender that writes to a <see cref="TextWriter"/>. |
| /// </para> |
| /// <para> |
| /// This appender may be used stand alone if initialized with an appropriate |
| /// writer, however it is typically used as a base class for an appender that |
| /// can open a <see cref="TextWriter"/> to write to. |
| /// </para> |
| /// </remarks> |
| /// <author>Nicko Cadell</author> |
| /// <author>Gert Driesen</author> |
| /// <author>Douglas de la Torre</author> |
| public class TextWriterAppender : AppenderSkeleton |
| { |
| /// <summary> |
| /// Gets or set whether the appender will flush at the end |
| /// of each append operation. |
| /// </summary> |
| /// <value> |
| /// <para> |
| /// The default behavior is to flush at the end of each |
| /// append operation. |
| /// </para> |
| /// <para> |
| /// If this option is set to <c>false</c>, then the underlying |
| /// stream can defer persisting the logging event to a later |
| /// time. |
| /// </para> |
| /// </value> |
| /// <remarks> |
| /// Avoiding the flush operation at the end of each append results in |
| /// a performance gain of 10 to 20 percent. However, there is a safety |
| /// trade-off involved in skipping flushing. Indeed, when flushing is |
| /// skipped, then it is likely that the last few log events will not |
| /// be recorded on disk when the application exits. This is a high |
| /// price to pay even for a 20% performance gain. |
| /// </remarks> |
| public bool ImmediateFlush { get; set; } = true; |
| |
| /// <summary> |
| /// Sets the <see cref="TextWriter"/> where the log output will go. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// The specified <see cref="TextWriter"/> must be open and writable. |
| /// </para> |
| /// <para> |
| /// The <see cref="TextWriter"/> will be closed when the appender |
| /// instance is closed. |
| /// </para> |
| /// <para> |
| /// <b>Note:</b> Logging to an unopened <see cref="TextWriter"/> will fail. |
| /// </para> |
| /// </remarks> |
| public virtual TextWriter? Writer |
| { |
| get => QuietWriter; |
| set |
| { |
| lock (_syncRoot) |
| { |
| Reset(); |
| if (value is not null) |
| { |
| QuietWriter = new QuietTextWriter(value, ErrorHandler); |
| WriteHeader(); |
| } |
| } |
| } |
| } |
| |
| /// <summary> |
| /// This method determines if there is a sense in attempting to append. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// This method checks if an output target has been set and if a |
| /// layout has been set. |
| /// </para> |
| /// </remarks> |
| /// <returns><c>false</c> if any of the preconditions fail.</returns> |
| protected override bool PreAppendCheck() |
| { |
| if (!base.PreAppendCheck()) |
| { |
| return false; |
| } |
| |
| if (QuietWriter is null) |
| { |
| // Allow subclass to lazily create the writer |
| PrepareWriter(); |
| |
| if (QuietWriter is null) |
| { |
| ErrorHandler.Error("No output stream or file set for the appender named [" + Name + "]."); |
| return false; |
| } |
| } |
| if (QuietWriter.Closed) |
| { |
| ErrorHandler.Error("Output stream for appender named [" + Name + "] has been closed."); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /// <summary> |
| /// This method is called by the <see cref="AppenderSkeleton.DoAppend(LoggingEvent)"/> |
| /// method. |
| /// </summary> |
| /// <param name="loggingEvent">The event to log.</param> |
| /// <remarks> |
| /// <para> |
| /// Writes a log statement to the output stream if the output stream exists |
| /// and is writable. |
| /// </para> |
| /// <para> |
| /// The format of the output will depend on the appender's layout. |
| /// </para> |
| /// </remarks> |
| protected override void Append(LoggingEvent loggingEvent) |
| { |
| if (QuietWriter is null) |
| { |
| return; |
| } |
| |
| RenderLoggingEvent(QuietWriter, loggingEvent); |
| |
| if (ImmediateFlush) |
| { |
| QuietWriter.Flush(); |
| } |
| } |
| |
| /// <summary> |
| /// This method is called by the <see cref="AppenderSkeleton.DoAppend(LoggingEvent[])"/> |
| /// method. |
| /// </summary> |
| /// <param name="loggingEvents">The array of events to log.</param> |
| /// <remarks> |
| /// <para> |
| /// This method writes all the bulk logged events to the output writer |
| /// before flushing the stream. |
| /// </para> |
| /// </remarks> |
| protected override void Append(LoggingEvent[] loggingEvents) |
| { |
| if (QuietWriter is null) |
| { |
| return; |
| } |
| loggingEvents.EnsureNotNull(); |
| foreach (LoggingEvent loggingEvent in loggingEvents) |
| { |
| RenderLoggingEvent(QuietWriter, loggingEvent); |
| } |
| |
| if (ImmediateFlush) |
| { |
| QuietWriter.Flush(); |
| } |
| } |
| |
| /// <summary> |
| /// Close this appender instance. The underlying stream or writer is also closed. |
| /// </summary> |
| /// <remarks> |
| /// Closed appenders cannot be reused. |
| /// </remarks> |
| protected override void OnClose() |
| { |
| lock (_syncRoot) |
| { |
| Reset(); |
| } |
| } |
| |
| /// <summary> |
| /// Gets or set the <see cref="IErrorHandler"/> and the underlying |
| /// <see cref="QuietTextWriter"/>, if any, for this appender. |
| /// </summary> |
| /// <value> |
| /// The <see cref="IErrorHandler"/> for this appender. |
| /// </value> |
| public override IErrorHandler ErrorHandler |
| { |
| get => base.ErrorHandler; |
| set |
| { |
| lock (_syncRoot) |
| { |
| if (value is null) |
| { |
| LogLog.Warn(_declaringType, "TextWriterAppender: You have tried to set a null error-handler."); |
| } |
| else |
| { |
| base.ErrorHandler = value; |
| if (QuietWriter is not null) |
| { |
| QuietWriter.ErrorHandler = value; |
| } |
| } |
| } |
| } |
| } |
| |
| /// <summary> |
| /// This appender requires a <see cref="Layout"/> to be set. |
| /// </summary> |
| protected override bool RequiresLayout => true; |
| |
| /// <summary> |
| /// Writes the footer and closes the underlying <see cref="TextWriter"/>. |
| /// </summary> |
| protected virtual void WriteFooterAndCloseWriter() |
| { |
| WriteFooter(); |
| CloseWriter(); |
| } |
| |
| /// <summary> |
| /// Closes the underlying <see cref="TextWriter"/>. |
| /// </summary> |
| protected virtual void CloseWriter() |
| { |
| if (QuietWriter is not null) |
| { |
| try |
| { |
| QuietWriter.Close(); |
| } |
| catch (Exception e) when (!e.IsFatal()) |
| { |
| ErrorHandler.Error($"Could not close writer [{QuietWriter}]", e); |
| // do need to invoke an error handler |
| // at this late stage |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Clears internal references to the underlying <see cref="TextWriter" /> |
| /// and other variables. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// Subclasses can override this method for an alternate closing behavior. |
| /// </para> |
| /// </remarks> |
| protected virtual void Reset() |
| { |
| WriteFooterAndCloseWriter(); |
| QuietWriter = null; |
| } |
| |
| /// <summary> |
| /// Writes a footer as produced by the embedded layout's <see cref="ILayout.Footer"/> property. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// Writes a footer as produced by the embedded layout's <see cref="ILayout.Footer"/> property. |
| /// </para> |
| /// </remarks> |
| protected virtual void WriteFooter() |
| { |
| if (Layout is not null && QuietWriter is not null && !QuietWriter.Closed) |
| { |
| string? f = Layout.Footer; |
| if (f is not null) |
| { |
| QuietWriter.Write(f); |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Writes a header produced by the embedded layout's <see cref="ILayout.Header"/> property. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// Writes a header produced by the embedded layout's <see cref="ILayout.Header"/> property. |
| /// </para> |
| /// </remarks> |
| protected virtual void WriteHeader() |
| { |
| if (Layout is not null && QuietWriter is not null && !QuietWriter.Closed) |
| { |
| if (Layout.Header is string h) |
| { |
| QuietWriter.Write(h); |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Called to allow a subclass to lazily initialize the writer |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// This method is called when an event is logged and the <see cref="Writer"/> or |
| /// <see cref="QuietWriter"/> have not been set. This allows a subclass to |
| /// attempt to initialize the writer multiple times. |
| /// </para> |
| /// </remarks> |
| protected virtual void PrepareWriter() |
| { |
| } |
| |
| /// <summary> |
| /// Gets or sets the <see cref="log4net.Util.QuietTextWriter"/> where logging events |
| /// will be written to. |
| /// </summary> |
| /// <value> |
| /// The <see cref="log4net.Util.QuietTextWriter"/> where logging events are written. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// This is the <see cref="log4net.Util.QuietTextWriter"/> where logging events |
| /// will be written to. |
| /// </para> |
| /// </remarks> |
| protected QuietTextWriter? QuietWriter { get; set; } |
| |
| private readonly object _syncRoot = new(); |
| |
| /// <summary> |
| /// The fully qualified type of the TextWriterAppender class. |
| /// </summary> |
| /// <remarks> |
| /// Used by the internal logger to record the Type of the |
| /// log message. |
| /// </remarks> |
| private static readonly Type _declaringType = typeof(TextWriterAppender); |
| |
| /// <summary> |
| /// Flushes any buffered log data. |
| /// </summary> |
| /// <param name="millisecondsTimeout">The maximum time to wait for logging events to be flushed.</param> |
| /// <returns><c>True</c> if all logging events were flushed successfully, else <c>false</c>.</returns> |
| public override bool Flush(int millisecondsTimeout) |
| { |
| // Nothing to do if ImmediateFlush is true |
| if (ImmediateFlush) |
| { |
| return true; |
| } |
| |
| // lock(this) will block any Appends while the buffer is flushed. |
| lock (_syncRoot) |
| { |
| QuietWriter?.Flush(); |
| } |
| |
| return true; |
| } |
| } |