#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 { #region Public Instance Constructors /// <summary> /// Initializes a new instance of the <see cref="TextWriterAppender" /> class. /// </summary> /// <remarks> /// <para> /// Default constructor. /// </para> /// </remarks> public TextWriterAppender() { } /// <summary> /// Initializes a new instance of the <see cref="TextWriterAppender" /> class and /// sets the output destination to a new <see cref="StreamWriter"/> initialized /// with the specified <see cref="Stream"/>. /// </summary> /// <param name="layout">The layout to use with this appender.</param> /// <param name="os">The <see cref="Stream"/> to output to.</param> /// <remarks> /// <para> /// Obsolete constructor. /// </para> /// </remarks> [Obsolete("Instead use the default constructor and set the Layout & Writer properties")] public TextWriterAppender(ILayout layout, Stream os) : this(layout, new StreamWriter(os)) { } /// <summary> /// Initializes a new instance of the <see cref="TextWriterAppender" /> class and sets /// the output destination to the specified <see cref="StreamWriter" />. /// </summary> /// <param name="layout">The layout to use with this appender</param> /// <param name="writer">The <see cref="TextWriter" /> to output to</param> /// <remarks> /// The <see cref="TextWriter" /> must have been previously opened. /// </remarks> /// <remarks> /// <para> /// Obsolete constructor. /// </para> /// </remarks> [Obsolete("Instead use the default constructor and set the Layout & Writer properties")] public TextWriterAppender(ILayout layout, TextWriter writer) { Layout = layout; Writer = writer; } #endregion #region Public Instance Properties /// <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 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 { return m_immediateFlush; } set { m_immediateFlush = value; } } /// <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> virtual public TextWriter Writer { get { return m_qtw; } set { lock(this) { Reset(); if (value != null) { m_qtw = new QuietTextWriter(value, ErrorHandler); WriteHeader(); } } } } #endregion Public Instance Properties #region Override implementation of AppenderSkeleton /// <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> override protected bool PreAppendCheck() { if (!base.PreAppendCheck()) { return false; } if (m_qtw == null) { // Allow subclass to lazily create the writer PrepareWriter(); if (m_qtw == null) { ErrorHandler.Error("No output stream or file set for the appender named ["+ Name +"]."); return false; } } if (m_qtw.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="M: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> override protected void Append(LoggingEvent loggingEvent) { RenderLoggingEvent(m_qtw, loggingEvent); if (m_immediateFlush) { m_qtw.Flush(); } } /// <summary> /// This method is called by the <see cref="M: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> override protected void Append(LoggingEvent[] loggingEvents) { foreach(LoggingEvent loggingEvent in loggingEvents) { RenderLoggingEvent(m_qtw, loggingEvent); } if (m_immediateFlush) { m_qtw.Flush(); } } /// <summary> /// Close this appender instance. The underlying stream or writer is also closed. /// </summary> /// <remarks> /// Closed appenders cannot be reused. /// </remarks> override protected void OnClose() { lock(this) { 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> override public IErrorHandler ErrorHandler { get { return base.ErrorHandler; } set { lock(this) { if (value == null) { LogLog.Warn(declaringType, "TextWriterAppender: You have tried to set a null error-handler."); } else { base.ErrorHandler = value; if (m_qtw != null) { m_qtw.ErrorHandler = value; } } } } } /// <summary> /// This appender requires a <see cref="Layout"/> to be set. /// </summary> /// <value><c>true</c></value> /// <remarks> /// <para> /// This appender requires a <see cref="Layout"/> to be set. /// </para> /// </remarks> override protected bool RequiresLayout { get { return true; } } #endregion Override implementation of AppenderSkeleton #region Protected Instance Methods /// <summary> /// Writes the footer and closes the underlying <see cref="TextWriter"/>. /// </summary> /// <remarks> /// <para> /// Writes the footer and closes the underlying <see cref="TextWriter"/>. /// </para> /// </remarks> virtual protected void WriteFooterAndCloseWriter() { WriteFooter(); CloseWriter(); } /// <summary> /// Closes the underlying <see cref="TextWriter"/>. /// </summary> /// <remarks> /// <para> /// Closes the underlying <see cref="TextWriter"/>. /// </para> /// </remarks> virtual protected void CloseWriter() { if (m_qtw != null) { try { m_qtw.Close(); } catch(Exception e) { ErrorHandler.Error("Could not close writer ["+m_qtw+"]", 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> virtual protected void Reset() { WriteFooterAndCloseWriter(); m_qtw = 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> virtual protected void WriteFooter() { if (Layout != null && m_qtw != null && !m_qtw.Closed) { string f = Layout.Footer; if (f != null) { m_qtw.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> virtual protected void WriteHeader() { if (Layout != null && m_qtw != null && !m_qtw.Closed) { string h = Layout.Header; if (h != null) { m_qtw.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> virtual protected 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 { return m_qtw; } set { m_qtw = value; } } #endregion Protected Instance Methods #region Private Instance Fields /// <summary> /// This is the <see cref="log4net.Util.QuietTextWriter"/> where logging events /// will be written to. /// </summary> private QuietTextWriter m_qtw; /// <summary> /// Immediate flush means that the underlying <see cref="TextWriter" /> /// or output stream will be flushed at the end of each append operation. /// </summary> /// <remarks> /// <para> /// Immediate flush is slower but ensures that each append request is /// actually written. If <see cref="ImmediateFlush"/> is set to /// <c>false</c>, then there is a good chance that the last few /// logging events are not actually persisted if and when the application /// crashes. /// </para> /// <para> /// The default value is <c>true</c>. /// </para> /// </remarks> private bool m_immediateFlush = true; #endregion Private Instance Fields #region Private Static Fields /// <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 readonly static Type declaringType = typeof(TextWriterAppender); #endregion Private Static Fields /// <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 (m_immediateFlush) return true; // lock(this) will block any Appends while the buffer is flushed. lock (this) { m_qtw.Flush(); } return true; } } }