#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 { /// /// 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 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. /// /// 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 /// /// 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. /// /// 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()); } } /// /// This appender requires a to be set. /// /// true /// /// /// This appender requires a to be set. /// /// override protected 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 } }