#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 // MONO 1.0 Beta mcs does not like #if !A && !B && !C syntax // .NET Compact Framework 1.0 has no support for EventLog #if !NETCF // SSCLI 1.0 has no support for EventLog #if !SSCLI using System; using System.Diagnostics; using System.Globalization; using log4net.Util; using log4net.Layout; using log4net.Core; namespace log4net.Appender { /// /// Writes events to the system event log. /// /// /// /// The appender will fail if you try to write using an event source that doesn't exist unless it is running with local administrator privileges. /// See also http://logging.apache.org/log4net/release/faq.html#trouble-EventLog /// /// /// The EventID of the event log entry can be /// set using the EventID property () /// on the . /// /// /// The Category of the event log entry can be /// set using the Category property () /// on the . /// /// /// There is a limit of 32K characters for an event log message /// /// /// When configuring the EventLogAppender a mapping can be /// specified to map a logging level to an event log entry type. For example: /// /// /// <mapping> /// <level value="ERROR" /> /// <eventLogEntryType value="Error" /> /// </mapping> /// <mapping> /// <level value="DEBUG" /> /// <eventLogEntryType value="Information" /> /// </mapping> /// /// /// The Level is the standard log4net logging level and eventLogEntryType can be any value /// from the enum, i.e.: /// /// Erroran error event /// Warninga warning event /// Informationan informational event /// /// /// /// Aspi Havewala /// Douglas de la Torre /// Nicko Cadell /// Gert Driesen /// Thomas Voss public class EventLogAppender : AppenderSkeleton { #region Public Instance Constructors /// /// Initializes a new instance of the class. /// /// /// /// Default constructor. /// /// public EventLogAppender() { m_applicationName = System.Threading.Thread.GetDomain().FriendlyName; m_logName = "Application"; // Defaults to application log m_machineName = "."; // Only log on the local machine } /// /// Initializes a new instance of the class /// with the specified . /// /// The to use with this appender. /// /// /// Obsolete constructor. /// /// [Obsolete("Instead use the default constructor and set the Layout property")] public EventLogAppender(ILayout layout) : this() { Layout = layout; } #endregion // Public Instance Constructors #region Public Instance Properties /// /// The name of the log where messages will be stored. /// /// /// The string name of the log where messages will be stored. /// /// /// This is the name of the log as it appears in the Event Viewer /// tree. The default value is to log into the Application /// log, this is where most applications write their events. However /// if you need a separate log for your application (or applications) /// then you should set the appropriately. /// This should not be used to distinguish your event log messages /// from those of other applications, the /// property should be used to distinguish events. This property should be /// used to group together events into a single log. /// /// public string LogName { get { return m_logName; } set { m_logName = value; } } /// /// Property used to set the Application name. This appears in the /// event logs when logging. /// /// /// The string used to distinguish events from different sources. /// /// /// Sets the event log source property. /// public string ApplicationName { get { return m_applicationName; } set { m_applicationName = value; } } /// /// This property is used to return the name of the computer to use /// when accessing the event logs. Currently, this is the current /// computer, denoted by a dot "." /// /// /// The string name of the machine holding the event log that /// will be logged into. /// /// /// This property cannot be changed. It is currently set to '.' /// i.e. the local machine. This may be changed in future. /// public string MachineName { get { return m_machineName; } set { /* Currently we do not allow the machine name to be changed */; } } /// /// Add a mapping of level to - done by the config file /// /// The mapping to add /// /// /// Add a mapping to this appender. /// Each mapping defines the event log entry type for a level. /// /// public void AddMapping(Level2EventLogEntryType mapping) { m_levelMapping.Add(mapping); } /// /// Gets or sets the used to write to the EventLog. /// /// /// The used to write to the EventLog. /// /// /// /// The system security context used to write to the EventLog. /// /// /// 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; } } /// /// Gets or sets the EventId to use unless one is explicitly specified via the LoggingEvent's properties. /// /// /// /// The EventID of the event log entry will normally be /// set using the EventID property () /// on the . /// This property provides the fallback value which defaults to 0. /// /// public int EventId { get { return m_eventId; } set { m_eventId = value; } } /// /// Gets or sets the Category to use unless one is explicitly specified via the LoggingEvent's properties. /// /// /// /// The Category of the event log entry will normally be /// set using the Category property () /// on the . /// This property provides the fallback value which defaults to 0. /// /// public short Category { get { return m_category; } set { m_category = value; } } #endregion // Public Instance Properties #region Implementation of IOptionHandler /// /// Initialize the appender based on the options set /// /// /// /// 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() { try { base.ActivateOptions(); if (m_securityContext == null) { m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this); } bool sourceAlreadyExists = false; string currentLogName = null; using (SecurityContext.Impersonate(this)) { sourceAlreadyExists = EventLog.SourceExists(m_applicationName); if (sourceAlreadyExists) { currentLogName = EventLog.LogNameFromSourceName(m_applicationName, m_machineName); } } if (sourceAlreadyExists && currentLogName != m_logName) { LogLog.Debug(declaringType, "Changing event source [" + m_applicationName + "] from log [" + currentLogName + "] to log [" + m_logName + "]"); } else if (!sourceAlreadyExists) { LogLog.Debug(declaringType, "Creating event source Source [" + m_applicationName + "] in log " + m_logName + "]"); } string registeredLogName = null; using (SecurityContext.Impersonate(this)) { if (sourceAlreadyExists && currentLogName != m_logName) { // // Re-register this to the current application if the user has changed // the application / logfile association // EventLog.DeleteEventSource(m_applicationName, m_machineName); CreateEventSource(m_applicationName, m_logName, m_machineName); registeredLogName = EventLog.LogNameFromSourceName(m_applicationName, m_machineName); } else if (!sourceAlreadyExists) { CreateEventSource(m_applicationName, m_logName, m_machineName); registeredLogName = EventLog.LogNameFromSourceName(m_applicationName, m_machineName); } } m_levelMapping.ActivateOptions(); LogLog.Debug(declaringType, "Source [" + m_applicationName + "] is registered to log [" + registeredLogName + "]"); } catch (System.Security.SecurityException ex) { ErrorHandler.Error("Caught a SecurityException trying to access the EventLog. Most likely the event source " + m_applicationName + " doesn't exist and must be created by a local administrator. Will disable EventLogAppender." + " See http://logging.apache.org/log4net/release/faq.html#trouble-EventLog", ex); Threshold = Level.Off; } } #endregion // Implementation of IOptionHandler /// /// Create an event log source /// /// /// Uses different API calls under NET_2_0 /// private static void CreateEventSource(string source, string logName, string machineName) { #if NET_2_0 EventSourceCreationData eventSourceCreationData = new EventSourceCreationData(source, logName); eventSourceCreationData.MachineName = machineName; EventLog.CreateEventSource(eventSourceCreationData); #else EventLog.CreateEventSource(source, logName, machineName); #endif } #region Override implementation of AppenderSkeleton /// /// This method is called by the /// method. /// /// the event to log /// /// Writes the event to the system event log using the /// . /// /// If the event has an EventID property (see ) /// set then this integer will be used as the event log event id. /// /// /// There is a limit of 32K characters for an event log message /// /// override protected void Append(LoggingEvent loggingEvent) { // // Write the resulting string to the event log system // int eventID = m_eventId; // Look for the EventID property object eventIDPropertyObj = loggingEvent.LookupProperty("EventID"); if (eventIDPropertyObj != null) { if (eventIDPropertyObj is int) { eventID = (int)eventIDPropertyObj; } else { string eventIDPropertyString = eventIDPropertyObj as string; if (eventIDPropertyString == null) { eventIDPropertyString = eventIDPropertyObj.ToString(); } if (eventIDPropertyString != null && eventIDPropertyString.Length > 0) { // Read the string property into a number int intVal; if (SystemInfo.TryParse(eventIDPropertyString, out intVal)) { eventID = intVal; } else { ErrorHandler.Error("Unable to parse event ID property [" + eventIDPropertyString + "]."); } } } } short category = m_category; // Look for the Category property object categoryPropertyObj = loggingEvent.LookupProperty("Category"); if (categoryPropertyObj != null) { if (categoryPropertyObj is short) { category = (short) categoryPropertyObj; } else { string categoryPropertyString = categoryPropertyObj as string; if (categoryPropertyString == null) { categoryPropertyString = categoryPropertyObj.ToString(); } if (categoryPropertyString != null && categoryPropertyString.Length > 0) { // Read the string property into a number short shortVal; if (SystemInfo.TryParse(categoryPropertyString, out shortVal)) { category = shortVal; } else { ErrorHandler.Error("Unable to parse event category property [" + categoryPropertyString + "]."); } } } } // Write to the event log try { string eventTxt = RenderLoggingEvent(loggingEvent); // There is a limit of about 32K characters for an event log message if (eventTxt.Length > MAX_EVENTLOG_MESSAGE_SIZE) { eventTxt = eventTxt.Substring(0, MAX_EVENTLOG_MESSAGE_SIZE); } EventLogEntryType entryType = GetEntryType(loggingEvent.Level); using(SecurityContext.Impersonate(this)) { EventLog.WriteEntry(m_applicationName, eventTxt, entryType, eventID, category); } } catch(Exception ex) { ErrorHandler.Error("Unable to write to event log [" + m_logName + "] using source [" + m_applicationName + "]", ex); } } /// /// 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 Instance Methods /// /// Get the equivalent for a /// /// the Level to convert to an EventLogEntryType /// The equivalent for a /// /// Because there are fewer applicable /// values to use in logging levels than there are in the /// this is a one way mapping. There is /// a loss of information during the conversion. /// virtual protected EventLogEntryType GetEntryType(Level level) { // see if there is a specified lookup. Level2EventLogEntryType entryType = m_levelMapping.Lookup(level) as Level2EventLogEntryType; if (entryType != null) { return entryType.EventLogEntryType; } // Use default behavior if (level >= Level.Error) { return EventLogEntryType.Error; } else if (level == Level.Warn) { return EventLogEntryType.Warning; } // Default setting return EventLogEntryType.Information; } #endregion // Protected Instance Methods #region Private Instance Fields /// /// The log name is the section in the event logs where the messages /// are stored. /// private string m_logName; /// /// Name of the application to use when logging. This appears in the /// application column of the event log named by . /// private string m_applicationName; /// /// The name of the machine which holds the event log. This is /// currently only allowed to be '.' i.e. the current machine. /// private string m_machineName; /// /// Mapping from level object to EventLogEntryType /// private LevelMapping m_levelMapping = new LevelMapping(); /// /// The security context to use for privileged calls /// private SecurityContext m_securityContext; /// /// The event ID to use unless one is explicitly specified via the LoggingEvent's properties. /// private int m_eventId = 0; /// /// The event category to use unless one is explicitly specified via the LoggingEvent's properties. /// private short m_category = 0; #endregion // Private Instance Fields #region Level2EventLogEntryType LevelMapping Entry /// /// A class to act as a mapping between the level that a logging call is made at and /// the color it should be displayed as. /// /// /// /// Defines the mapping between a level and its event log entry type. /// /// public class Level2EventLogEntryType : LevelMappingEntry { private EventLogEntryType m_entryType; /// /// The for this entry /// /// /// /// Required property. /// The for this entry /// /// public EventLogEntryType EventLogEntryType { get { return m_entryType; } set { m_entryType = value; } } } #endregion // LevelColors LevelMapping Entry #region Private Static Fields /// /// The fully qualified type of the EventLogAppender class. /// /// /// Used by the internal logger to record the Type of the /// log message. /// private readonly static Type declaringType = typeof(EventLogAppender); /// /// The maximum size supported by default. /// /// /// http://msdn.microsoft.com/en-us/library/xzwc042w(v=vs.100).aspx /// The 32766 documented max size is two bytes shy of 32K (I'm assuming 32766 /// may leave space for a two byte null terminator of #0#0). The 32766 max /// length is what the .NET 4.0 source code checks for, but this is WRONG! /// Strings with a length > 31839 on Windows Vista or higher can CORRUPT /// the event log! See: System.Diagnostics.EventLogInternal.InternalWriteEvent() /// for the use of the 32766 max size. /// private readonly static int MAX_EVENTLOG_MESSAGE_SIZE_DEFAULT = 32766; /// /// The maximum size supported by a windows operating system that is vista /// or newer. /// /// /// See ReportEvent API: /// http://msdn.microsoft.com/en-us/library/aa363679(VS.85).aspx /// ReportEvent's lpStrings parameter: /// "A pointer to a buffer containing an array of /// null-terminated strings that are merged into the message before Event Viewer /// displays the string to the user. This parameter must be a valid pointer /// (or NULL), even if wNumStrings is zero. Each string is limited to 31,839 characters." /// /// Going beyond the size of 31839 will (at some point) corrupt the event log on Windows /// Vista or higher! It may succeed for a while...but you will eventually run into the /// error: "System.ComponentModel.Win32Exception : A device attached to the system is /// not functioning", and the event log will then be corrupt (I was able to corrupt /// an event log using a length of 31877 on Windows 7). /// /// The max size for Windows Vista or higher is documented here: /// http://msdn.microsoft.com/en-us/library/xzwc042w(v=vs.100).aspx. /// Going over this size may succeed a few times but the buffer will overrun and /// eventually corrupt the log (based on testing). /// /// The maxEventMsgSize size is based on the max buffer size of the lpStrings parameter of the ReportEvent API. /// The documented max size for EventLog.WriteEntry for Windows Vista and higher is 31839, but I'm leaving room for a /// terminator of #0#0, as we cannot see the source of ReportEvent (though we could use an API monitor to examine the /// buffer, given enough time). /// private readonly static int MAX_EVENTLOG_MESSAGE_SIZE_VISTA_OR_NEWER = 31839 - 2; /// /// The maximum size that the operating system supports for /// a event log message. /// /// /// Used to determine the maximum string length that can be written /// to the operating system event log and eventually truncate a string /// that exceeds the limits. /// private readonly static int MAX_EVENTLOG_MESSAGE_SIZE = GetMaxEventLogMessageSize(); /// /// This method determines the maximum event log message size allowed for /// the current environment. /// /// private static int GetMaxEventLogMessageSize() { if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 6) return MAX_EVENTLOG_MESSAGE_SIZE_VISTA_OR_NEWER; return MAX_EVENTLOG_MESSAGE_SIZE_DEFAULT; } #endregion Private Static Fields } } #endif // !SSCLI #endif // !NETCF