blob: 5df481984ab11e31d791d7d2e48200b61fa402a9 [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.Text;
using System.IO;
using log4net.Layout;
using log4net.Core;
using log4net.Util;
namespace log4net.Appender
{
/// <summary>
/// Send an email when a specific logging event occurs, typically on errors
/// or fatal errors. Rather than sending via smtp it writes a file into the
/// directory specified by <see cref="PickupDir"/>. This allows services such
/// as the IIS SMTP agent to manage sending the messages.
/// </summary>
/// <remarks>
/// <para>
/// The configuration for this appender is identical to that of the <c>SMTPAppender</c>,
/// except that instead of specifying the <c>SMTPAppender.SMTPHost</c> you specify
/// <see cref="PickupDir"/>.
/// </para>
/// <para>
/// The number of logging events delivered in this e-mail depend on
/// the value of <see cref="BufferingAppenderSkeleton.BufferSize"/> option. The
/// <see cref="SmtpPickupDirAppender"/> keeps only the last
/// <see cref="BufferingAppenderSkeleton.BufferSize"/> logging events in its
/// cyclic buffer. This keeps memory requirements at a reasonable level while
/// still delivering useful application context.
/// </para>
/// </remarks>
/// <author>Niall Daley</author>
/// <author>Nicko Cadell</author>
public class SmtpPickupDirAppender : BufferingAppenderSkeleton
{
#region Public Instance Constructors
/// <summary>
/// Default constructor
/// </summary>
/// <remarks>
/// <para>
/// Default constructor
/// </para>
/// </remarks>
public SmtpPickupDirAppender()
{
m_fileExtension = string.Empty; // Default to empty string, not null
}
#endregion Public Instance Constructors
#region Public Instance Properties
/// <summary>
/// Gets or sets a semicolon-delimited list of recipient e-mail addresses.
/// </summary>
/// <value>
/// A semicolon-delimited list of e-mail addresses.
/// </value>
/// <remarks>
/// <para>
/// A semicolon-delimited list of e-mail addresses.
/// </para>
/// </remarks>
public string To
{
get { return m_to; }
set { m_to = value; }
}
/// <summary>
/// Gets or sets the e-mail address of the sender.
/// </summary>
/// <value>
/// The e-mail address of the sender.
/// </value>
/// <remarks>
/// <para>
/// The e-mail address of the sender.
/// </para>
/// </remarks>
public string From
{
get { return m_from; }
set { m_from = value; }
}
/// <summary>
/// Gets or sets the subject line of the e-mail message.
/// </summary>
/// <value>
/// The subject line of the e-mail message.
/// </value>
/// <remarks>
/// <para>
/// The subject line of the e-mail message.
/// </para>
/// </remarks>
public string Subject
{
get { return m_subject; }
set { m_subject = value; }
}
/// <summary>
/// Gets or sets the path to write the messages to.
/// </summary>
/// <remarks>
/// <para>
/// Gets or sets the path to write the messages to. This should be the same
/// as that used by the agent sending the messages.
/// </para>
/// </remarks>
public string PickupDir
{
get { return m_pickupDir; }
set { m_pickupDir = value; }
}
/// <summary>
/// Gets or sets the file extension for the generated files
/// </summary>
/// <value>
/// The file extension for the generated files
/// </value>
/// <remarks>
/// <para>
/// The file extension for the generated files
/// </para>
/// </remarks>
public string FileExtension
{
get { return m_fileExtension; }
set
{
m_fileExtension = value;
if (m_fileExtension == null)
{
m_fileExtension = string.Empty;
}
// Make sure any non empty extension starts with a dot
#if NET_2_0 || MONO_2_0
if (!string.IsNullOrEmpty(m_fileExtension) && !m_fileExtension.StartsWith("."))
#else
if (m_fileExtension != null && m_fileExtension.Length > 0 && !m_fileExtension.StartsWith("."))
#endif
{
m_fileExtension = "." + m_fileExtension;
}
}
}
/// <summary>
/// Gets or sets the <see cref="SecurityContext"/> used to write to the pickup directory.
/// </summary>
/// <value>
/// The <see cref="SecurityContext"/> used to write to the pickup directory.
/// </value>
/// <remarks>
/// <para>
/// Unless a <see cref="SecurityContext"/> specified here for this appender
/// the <see cref="SecurityContextProvider.DefaultProvider"/> is queried for the
/// security context to use. The default behavior is to use the security context
/// of the current thread.
/// </para>
/// </remarks>
public SecurityContext SecurityContext
{
get { return m_securityContext; }
set { m_securityContext = value; }
}
#endregion Public Instance Properties
#region Override implementation of BufferingAppenderSkeleton
/// <summary>
/// Sends the contents of the cyclic buffer as an e-mail message.
/// </summary>
/// <param name="events">The logging events to send.</param>
/// <remarks>
/// <para>
/// Sends the contents of the cyclic buffer as an e-mail message.
/// </para>
/// </remarks>
override protected void SendBuffer(LoggingEvent[] events)
{
// Note: this code already owns the monitor for this
// appender. This frees us from needing to synchronize again.
try
{
string filePath = null;
StreamWriter writer = null;
// Impersonate to open the file
using(SecurityContext.Impersonate(this))
{
filePath = Path.Combine(m_pickupDir, SystemInfo.NewGuid().ToString("N") + m_fileExtension);
writer = File.CreateText(filePath);
}
if (writer == null)
{
ErrorHandler.Error("Failed to create output file for writing ["+filePath+"]", null, ErrorCode.FileOpenFailure);
}
else
{
using(writer)
{
writer.WriteLine("To: " + m_to);
writer.WriteLine("From: " + m_from);
writer.WriteLine("Subject: " + m_subject);
writer.WriteLine("Date: " + DateTime.UtcNow.ToString("r"));
writer.WriteLine("");
string t = Layout.Header;
if (t != null)
{
writer.Write(t);
}
for(int i = 0; i < events.Length; i++)
{
// Render the event and append the text to the buffer
RenderLoggingEvent(writer, events[i]);
}
t = Layout.Footer;
if (t != null)
{
writer.Write(t);
}
writer.WriteLine("");
writer.WriteLine(".");
}
}
}
catch(Exception e)
{
ErrorHandler.Error("Error occurred while sending e-mail notification.", e);
}
}
#endregion Override implementation of BufferingAppenderSkeleton
#region Override implementation of AppenderSkeleton
/// <summary>
/// Activate the options on this appender.
/// </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>
override public void ActivateOptions()
{
base.ActivateOptions();
if (m_securityContext == null)
{
m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);
}
using(SecurityContext.Impersonate(this))
{
m_pickupDir = ConvertToFullPath(m_pickupDir.Trim());
}
}
/// <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 Static Methods
/// <summary>
/// Convert a path into a fully qualified path.
/// </summary>
/// <param name="path">The path to convert.</param>
/// <returns>The fully qualified path.</returns>
/// <remarks>
/// <para>
/// Converts the path specified to a fully
/// qualified path. If the path is relative it is
/// taken as relative from the application base
/// directory.
/// </para>
/// </remarks>
protected static string ConvertToFullPath(string path)
{
return SystemInfo.ConvertToFullPath(path);
}
#endregion Protected Static Methods
#region Private Instance Fields
private string m_to;
private string m_from;
private string m_subject;
private string m_pickupDir;
private string m_fileExtension;
/// <summary>
/// The security context to use for privileged calls
/// </summary>
private SecurityContext m_securityContext;
#endregion Private Instance Fields
}
}