#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.Core;
using log4net.Util;
namespace log4net.Appender
{
///
/// 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 . This allows services such
/// as the IIS SMTP agent to manage sending the messages.
///
///
///
/// The configuration for this appender is identical to that of the SMTPAppender,
/// except that instead of specifying the SMTPAppender.SMTPHost you specify
/// .
///
///
/// The number of logging events delivered in this e-mail depend on
/// the value of option. The
/// keeps only the last
/// logging events in its
/// cyclic buffer. This keeps memory requirements at a reasonable level while
/// still delivering useful application context.
///
///
/// Niall Daley
/// Nicko Cadell
public class SmtpPickupDirAppender : BufferingAppenderSkeleton
{
#region Public Instance Constructors
///
/// Default constructor
///
///
///
/// Default constructor
///
///
public SmtpPickupDirAppender()
{
m_fileExtension = string.Empty; // Default to empty string, not null
}
#endregion Public Instance Constructors
#region Public Instance Properties
///
/// Gets or sets a semicolon-delimited list of recipient e-mail addresses.
///
///
/// A semicolon-delimited list of e-mail addresses.
///
///
///
/// A semicolon-delimited list of e-mail addresses.
///
///
public string To
{
get { return m_to; }
set { m_to = value; }
}
///
/// Gets or sets the e-mail address of the sender.
///
///
/// The e-mail address of the sender.
///
///
///
/// The e-mail address of the sender.
///
///
public string From
{
get { return m_from; }
set { m_from = value; }
}
///
/// Gets or sets the subject line of the e-mail message.
///
///
/// The subject line of the e-mail message.
///
///
///
/// The subject line of the e-mail message.
///
///
public string Subject
{
get { return m_subject; }
set { m_subject = value; }
}
///
/// Gets or sets the path to write the messages to.
///
///
///
/// Gets or sets the path to write the messages to. This should be the same
/// as that used by the agent sending the messages.
///
///
public string PickupDir
{
get { return m_pickupDir; }
set { m_pickupDir = value; }
}
///
/// Gets or sets the file extension for the generated files
///
///
/// The file extension for the generated files
///
///
///
/// The file extension for the generated files
///
///
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 || NETSTANDARD
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;
}
}
}
///
/// Gets or sets the used to write to the pickup directory.
///
///
/// The used to write to the pickup directory.
///
///
///
/// Unless a specified here for this appender
/// the is queried for the
/// security context to use. The default behavior is to use the security context
/// of the current thread.
///
///
public SecurityContext SecurityContext
{
get { return m_securityContext; }
set { m_securityContext = value; }
}
#endregion Public Instance Properties
#region Override implementation of BufferingAppenderSkeleton
///
/// Sends the contents of the cyclic buffer as an e-mail message.
///
/// The logging events to send.
///
///
/// Sends the contents of the cyclic buffer as an e-mail message.
///
///
protected override 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
///
/// Activate the options on this appender.
///
///
///
/// 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.
///
///
public override void ActivateOptions()
{
base.ActivateOptions();
if (m_securityContext == null)
{
m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);
}
using(SecurityContext.Impersonate(this))
{
m_pickupDir = ConvertToFullPath(m_pickupDir.Trim());
}
}
///
/// This appender requires a to be set.
///
/// true
///
///
/// This appender requires a to be set.
///
///
protected override bool RequiresLayout
{
get { return true; }
}
#endregion Override implementation of AppenderSkeleton
#region Protected Static Methods
///
/// Convert a path into a fully qualified path.
///
/// The path to convert.
/// The fully qualified path.
///
///
/// Converts the path specified to a fully
/// qualified path. If the path is relative it is
/// taken as relative from the application base
/// directory.
///
///
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;
///
/// The security context to use for privileged calls
///
private SecurityContext m_securityContext;
#endregion Private Instance Fields
}
}