nicko 2004/09/14 14:00:36
Added: src/Appender LocalSyslogAppender.cs RemoteSyslogAppender.cs Log: Added LocalSyslogAppender and RemoteSyslogAppender based on Rob Lyon's contribution. LocalSyslogAppender uses libc calls to write to the local syslog service. RemoteSyslogAppender uses UDP port 514 to send messages to a remote syslogd. Revision Changes Path 1.1 logging-log4net/src/Appender/LocalSyslogAppender.cs Index: LocalSyslogAppender.cs =================================================================== #region Copyright & License /* * Copyright 2004 The Apache Software Foundation * * Licensed 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.Runtime.InteropServices; using log4net.Core; using log4net.Appender; using log4net.Util; using log4net.Layout; namespace log4net.Appender { /// <summary> /// Logs events to a local syslog service. /// </summary> /// <remarks> /// <note> /// This appender uses the POSIX libc library functions <c>openlog</c>, <c>syslog</c>, and <c>closelog</c>. /// If these functions are not available on the local system then this appender will not work! /// </note> /// <para> /// The functions <c>openlog</c>, <c>syslog</c>, and <c>closelog</c> are specified in SUSv2 and /// POSIX 1003.1-2001 standards. These are used to log messages to the local syslog service. /// </para> /// <para> /// This appender talks to a local syslog service. If you need to log to a remote syslog /// daemon and you cannot configure your local syslog service to do this you may be /// able to use the <see cref="RemoteSyslogAppender"/> to log via UDP. /// </para> /// <para> /// Syslog messages must have a facility and and a severity. The severity /// is derived from the Level of the logging event. /// The facility must be chosen from the set of defined syslog /// <see cref="SyslogFacility"/> values. The facilities list is predefined /// and cannot be extended. /// </para> /// <para> /// An identifier is specified with each log message. This can be specified /// by setting the <see cref="Identity"/> property. The identity (also know /// as the tag) must not contain white space. The default value for the /// identity is the application name (from <see cref="SystemInfo.ApplicationFriendlyName"/>). /// </para> /// </remarks> /// <author>Rob Lyon</author> /// <author>Nicko Cadell</author> public class LocalSyslogAppender : AppenderSkeleton { #region Enumerations /// <summary> /// syslog severities /// </summary> public enum SyslogSeverity { /// <summary> /// system is unusable /// </summary> Emergency = 0, /// <summary> /// action must be taken immediately /// </summary> Alert = 1, /// <summary> /// critical conditions /// </summary> Critical = 2, /// <summary> /// error conditions /// </summary> Error = 3, /// <summary> /// warning conditions /// </summary> Warning = 4, /// <summary> /// normal but significant condition /// </summary> Notice = 5, /// <summary> /// informational /// </summary> Informational = 6, /// <summary> /// debug-level messages /// </summary> Debug = 7 }; /// <summary> /// syslog facilities /// </summary> public enum SyslogFacility { /// <summary> /// kernel messages /// </summary> Kernel = 0, /// <summary> /// random user-level messages /// </summary> User = 1, /// <summary> /// mail system /// </summary> Mail = 2, /// <summary> /// system daemons /// </summary> Daemons = 3, /// <summary> /// security/authorization messages /// </summary> Authorization = 4, /// <summary> /// messages generated internally by syslogd /// </summary> Syslog = 5, /// <summary> /// line printer subsystem /// </summary> Printer = 6, /// <summary> /// network news subsystem /// </summary> News = 7, /// <summary> /// UUCP subsystem /// </summary> Uucp = 8, /// <summary> /// clock (cron/at) daemon /// </summary> Clock = 9, /// <summary> /// security/authorization messages (private) /// </summary> Authorization2 = 10, /// <summary> /// ftp daemon /// </summary> Ftp = 11, /// <summary> /// NTP subsystem /// </summary> Ntp = 12, /// <summary> /// log audit /// </summary> Audit = 13, /// <summary> /// log alert /// </summary> Alert = 14, /// <summary> /// clock daemon /// </summary> Clock2 = 15, /// <summary> /// reserved for local use /// </summary> Local0 = 16, /// <summary> /// reserved for local use /// </summary> Local1 = 17, /// <summary> /// reserved for local use /// </summary> Local2 = 18, /// <summary> /// reserved for local use /// </summary> Local3 = 19, /// <summary> /// reserved for local use /// </summary> Local4 = 20, /// <summary> /// reserved for local use /// </summary> Local5 = 21, /// <summary> /// reserved for local use /// </summary> Local6 = 22, /// <summary> /// reserved for local use /// </summary> Local7 = 23 } #endregion Enumerations #region Public Instance Constructors /// <summary> /// Initializes a new instance of the <see cref="LocalSyslogAppender" /> class. /// </summary> /// <remarks> /// This instance of the <see cref="LocalSyslogAppender" /> class is set up to write /// to a local syslog service. /// </remarks> public LocalSyslogAppender() { } #endregion Public Instance Constructors #region Public Instance Properties /// <summary> /// Message identity /// </summary> /// <remarks> /// <para> /// An identifier is specified with each log message. This can be specified /// by setting the <see cref="Identity"/> property. The identity (also know /// as the tag) must not contain white space. The default value for the /// identity is the application name (from <see cref="SystemInfo.ApplicationFriendlyName"/>). /// </para> /// </remarks> public string Identity { get { return m_identity; } set { m_identity = value; } } /// <summary> /// Syslog facility /// </summary> /// <remarks> /// Set to one of the <see cref="SyslogFacility"/> values. The list of /// facilities is predefined and cannot be extended. The default value /// is <see cref="SyslogFacility.User"/>. /// </remarks> public SyslogFacility Facility { get { return m_facility; } set { m_facility = value; } } #endregion Public Instance Properties /// <summary> /// Add a mapping of level to severity /// </summary> /// <param name="mapping">The mapping to add</param> public void AddMapping(LevelSeverity mapping) { m_levelMapping.Add(mapping); } #region IOptionHandler Implementation /// <summary> /// Initialize the appender based on the options set. /// </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> public override void ActivateOptions() { base.ActivateOptions(); m_levelMapping.ActivateOptions(); string identString = m_identity; if (identString == null) { // Set to app name by default identString = SystemInfo.ApplicationFriendlyName; } // create the native heap ansi string. Note this is a copy of our string // so we do not need to hold on to the string itself, holding on to the // handle will keep the heap ansi string alive. m_handleToIdentity = Marshal.StringToHGlobalAnsi(identString); // open syslog openlog(m_handleToIdentity, 1, m_facility); } #endregion IOptionHandler Implementation #region AppenderSkeleton Implementation /// <summary> /// This method is called by the <see cref="AppenderSkeleton.DoAppend"/> method. /// </summary> /// <param name="loggingEvent">The event to log.</param> /// <remarks> /// <para> /// Writes the event to a remote syslog daemon. /// </para> /// <para> /// The format of the output will depend on the appender's layout. /// </para> /// </remarks> protected override void Append(LoggingEvent loggingEvent) { int priority = GeneratePriority(m_facility, GetSeverity(loggingEvent.Level)); string message = RenderLoggingEvent(loggingEvent); syslog(priority, message); } /// <summary> /// Close the syslog when the appender is closed /// </summary> protected override void OnClose() { base.OnClose(); try { // close syslog closelog(); } catch(DllNotFoundException) { // Ignore dll not found at this point } if (m_handleToIdentity != IntPtr.Zero) { // free global ident Marshal.FreeHGlobal(m_handleToIdentity); } } /// <summary> /// This appender requires a <see cref="AppenderSkeleton.Layout"/> to be set. /// </summary> /// <value><c>true</c></value> override protected bool RequiresLayout { get { return true; } } #endregion AppenderSkeleton Implementation #region Protected Members /// <summary> /// Translates a log4net level to a syslog severity. /// </summary> /// <param name="level">A log4net level.</param> /// <returns>A syslog severity.</returns> virtual protected SyslogSeverity GetSeverity(Level level) { LevelSeverity levelSeverity = m_levelMapping.Lookup(level) as LevelSeverity; if (levelSeverity != null) { return levelSeverity.Severity; } // // Fallback to sensible default values // if (level >= Level.Alert) { return SyslogSeverity.Alert; } else if (level >= Level.Critical) { return SyslogSeverity.Critical; } else if (level >= Level.Error) { return SyslogSeverity.Error; } else if (level >= Level.Warn) { return SyslogSeverity.Warning; } else if (level >= Level.Notice) { return SyslogSeverity.Notice; } else if (level >= Level.Info) { return SyslogSeverity.Informational; } // Default setting return SyslogSeverity.Debug; } #endregion Protected Members #region Public Static Members /// <summary> /// Generate a syslog priority. /// </summary> /// <param name="facility">The syslog facility.</param> /// <param name="severity">The syslog severity.</param> /// <returns>A syslog priority.</returns> public static int GeneratePriority(SyslogFacility facility, SyslogSeverity severity) { return ((int)facility * 8) + (int)severity; } #endregion Public Static Members #region Private Instances Fields /// <summary> /// The facility. The default facility is <see cref="SyslogFacility.User"/>. /// </summary> private SyslogFacility m_facility = SyslogFacility.User; /// <summary> /// The message identity /// </summary> private string m_identity; /// <summary> /// Marshaled handle to the identity string. We have to hold on to the /// string as the <c>openlog</c> and <c>syslog</c> APIs just hold the /// pointer to the ident and dereference it for each log message. /// </summary> private IntPtr m_handleToIdentity = IntPtr.Zero; /// <summary> /// Mapping from level object to syslog severity /// </summary> private LevelMapping m_levelMapping = new LevelMapping(); #endregion Private Instances Fields #region External Members /// <summary> /// Open connection to system logger. /// </summary> [DllImport("libc")] public static extern void openlog(IntPtr ident, int option, SyslogFacility facility); /// <summary> /// Generate a log message. /// </summary> [DllImport("libc")] public static extern void syslog(int priority, string message); /// <summary> /// Close descriptor used to write to system logger. /// </summary> [DllImport("libc")] public static extern void closelog(); #endregion #region LevelSeverity LevelMapping Entry /// <summary> /// A class to act as a mapping between the level that a logging call is made at and /// the syslog severity that is should be logged at. /// </summary> public class LevelSeverity : LevelMappingEntry { private SyslogSeverity m_severity; /// <summary> /// The mapped syslog severity for the specified level /// </summary> public SyslogSeverity Severity { get { return m_severity; } set { m_severity = value; } } } #endregion // LevelSeverity LevelMapping Entry } } 1.1 logging-log4net/src/Appender/RemoteSyslogAppender.cs Index: RemoteSyslogAppender.cs =================================================================== #region Copyright & License /* * Copyright 2004 The Apache Software Foundation * * Licensed 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 log4net.Core; using log4net.Appender; using log4net.Util; using log4net.Layout; namespace log4net.Appender { /// <summary> /// Logs events to a remote syslog daemon. /// </summary> /// <remarks> /// <para> /// The BSD syslog protocol is used to remotely log to /// a syslog daemon. The syslogd listens for for messages /// on UDP port 514. /// </para> /// <para> /// The syslog UDP protocol is not authenticated. Most syslog daemons /// do not accept remote log messages because of the security implications. /// You may be able to use the LocalSyslogAppender to talk to a local /// syslog service. /// </para> /// <para> /// There is an RFC 3164 that claims to document the BSD Syslog Protocol. /// This RFC can be seen here: http://www.faqs.org/rfcs/rfc3164.html. /// This appender generates what the RFC calls an "Original Device Message", /// i.e. does not include the TIMESTAMP or HOSTNAME fields. By observation /// this format of message will be accepted by all current syslog daemon /// implementations. The daemon will attach the current time and the source /// hostname or IP address to any messages received. /// </para> /// <para> /// Syslog messages must have a facility and and a severity. The severity /// is derived from the Level of the logging event. /// The facility must be chosen from the set of defined syslog /// <see cref="SyslogFacility"/> values. The facilities list is predefined /// and cannot be extended. /// </para> /// <para> /// An identifier is specified with each log message. This can be specified /// by setting the <see cref="Identity"/> property. The identity (also know /// as the tag) must not contain white space. The default value for the /// identity is the application name (from <see cref="LoggingEvent.Domain"/>). /// </para> /// </remarks> /// <author>Rob Lyon</author> /// <author>Nicko Cadell</author> public class RemoteSyslogAppender : UdpAppender { /// <summary> /// Syslog port 514 /// </summary> private const int DefaultSyslogPort = 514; #region Enumerations /// <summary> /// syslog severities /// </summary> public enum SyslogSeverity { /// <summary> /// system is unusable /// </summary> Emergency = 0, /// <summary> /// action must be taken immediately /// </summary> Alert = 1, /// <summary> /// critical conditions /// </summary> Critical = 2, /// <summary> /// error conditions /// </summary> Error = 3, /// <summary> /// warning conditions /// </summary> Warning = 4, /// <summary> /// normal but significant condition /// </summary> Notice = 5, /// <summary> /// informational /// </summary> Informational = 6, /// <summary> /// debug-level messages /// </summary> Debug = 7 }; /// <summary> /// syslog facilities /// </summary> public enum SyslogFacility { /// <summary> /// kernel messages /// </summary> Kernel = 0, /// <summary> /// random user-level messages /// </summary> User = 1, /// <summary> /// mail system /// </summary> Mail = 2, /// <summary> /// system daemons /// </summary> Daemons = 3, /// <summary> /// security/authorization messages /// </summary> Authorization = 4, /// <summary> /// messages generated internally by syslogd /// </summary> Syslog = 5, /// <summary> /// line printer subsystem /// </summary> Printer = 6, /// <summary> /// network news subsystem /// </summary> News = 7, /// <summary> /// UUCP subsystem /// </summary> Uucp = 8, /// <summary> /// clock (cron/at) daemon /// </summary> Clock = 9, /// <summary> /// security/authorization messages (private) /// </summary> Authorization2 = 10, /// <summary> /// ftp daemon /// </summary> Ftp = 11, /// <summary> /// NTP subsystem /// </summary> Ntp = 12, /// <summary> /// log audit /// </summary> Audit = 13, /// <summary> /// log alert /// </summary> Alert = 14, /// <summary> /// clock daemon /// </summary> Clock2 = 15, /// <summary> /// reserved for local use /// </summary> Local0 = 16, /// <summary> /// reserved for local use /// </summary> Local1 = 17, /// <summary> /// reserved for local use /// </summary> Local2 = 18, /// <summary> /// reserved for local use /// </summary> Local3 = 19, /// <summary> /// reserved for local use /// </summary> Local4 = 20, /// <summary> /// reserved for local use /// </summary> Local5 = 21, /// <summary> /// reserved for local use /// </summary> Local6 = 22, /// <summary> /// reserved for local use /// </summary> Local7 = 23 } #endregion Enumerations #region Public Instance Constructors /// <summary> /// Initializes a new instance of the <see cref="RemoteSyslogAppender" /> class. /// </summary> /// <remarks> /// This instance of the <see cref="RemoteSyslogAppender" /> class is set up to write /// to a remote syslog daemon. /// </remarks> public RemoteSyslogAppender() { // syslog udp defaults this.RemotePort = DefaultSyslogPort; this.RemoteAddress = System.Net.IPAddress.Parse("127.0.0.1"); this.Encoding = System.Text.Encoding.ASCII; } #endregion Public Instance Constructors #region Public Instance Properties /// <summary> /// Message identity /// </summary> /// <remarks> /// <para> /// An identifier is specified with each log message. This can be specified /// by setting the <see cref="Identity"/> property. The identity (also know /// as the tag) must not contain white space. The default value for the /// identity is the application name (from <see cref="LoggingEvent.Domain"/>). /// </para> /// </remarks> public PatternLayout Identity { get { return m_identity; } set { m_identity = value; } } /// <summary> /// Syslog facility /// </summary> /// <remarks> /// Set to one of the <see cref="SyslogFacility"/> values. The list of /// facilities is predefined and cannot be extended. The default value /// is <see cref="SyslogFacility.User"/>. /// </remarks> public SyslogFacility Facility { get { return m_facility; } set { m_facility = value; } } #endregion Public Instance Properties /// <summary> /// Add a mapping of level to severity /// </summary> /// <param name="mapping">The mapping to add</param> public void AddMapping(LevelSeverity mapping) { m_levelMapping.Add(mapping); } #region AppenderSkeleton Implementation /// <summary> /// This method is called by the <see cref="AppenderSkeleton.DoAppend"/> method. /// </summary> /// <param name="loggingEvent">The event to log.</param> /// <remarks> /// <para> /// Writes the event to a remote syslog daemon. /// </para> /// <para> /// The format of the output will depend on the appender's layout. /// </para> /// </remarks> protected override void Append(LoggingEvent loggingEvent) { try { System.IO.StringWriter writer = new System.IO.StringWriter(); // Priority int priority = GeneratePriority(m_facility, GetSeverity(loggingEvent.Level)); writer.Write('<'); writer.Write(priority); writer.Write('>'); // Identity if (m_identity != null) { m_identity.Format(writer, loggingEvent); } else { writer.Write(loggingEvent.Domain); } writer.Write(": "); // Message. The message goes after the tag/identity RenderLoggingEvent(writer, loggingEvent); // Grab as a byte array string fullMessage = writer.ToString(); Byte [] buffer = this.Encoding.GetBytes(fullMessage.ToCharArray()); this.Client.Send(buffer, buffer.Length, this.RemoteEndPoint); } catch (Exception e) { ErrorHandler.Error( "Unable to send logging event to remote syslog " + this.RemoteAddress.ToString() + " on port " + this.RemotePort + ".", e, ErrorCode.WriteFailure); } } /// <summary> /// Initialise the options for this appender /// </summary> /// <remarks> /// <para> /// Initialise the level to syslog severity mappings set on this appender. /// </para> /// </remarks> public override void ActivateOptions() { base.ActivateOptions(); m_levelMapping.ActivateOptions(); } #endregion AppenderSkeleton Implementation #region Protected Members /// <summary> /// Translates a log4net level to a syslog severity. /// </summary> /// <param name="level">A log4net level.</param> /// <returns>A syslog severity.</returns> virtual protected SyslogSeverity GetSeverity(Level level) { LevelSeverity levelSeverity = m_levelMapping.Lookup(level) as LevelSeverity; if (levelSeverity != null) { return levelSeverity.Severity; } // // Fallback to sensible default values // if (level >= Level.Alert) { return SyslogSeverity.Alert; } else if (level >= Level.Critical) { return SyslogSeverity.Critical; } else if (level >= Level.Error) { return SyslogSeverity.Error; } else if (level >= Level.Warn) { return SyslogSeverity.Warning; } else if (level >= Level.Notice) { return SyslogSeverity.Notice; } else if (level >= Level.Info) { return SyslogSeverity.Informational; } // Default setting return SyslogSeverity.Debug; } #endregion Protected Members #region Public Static Members /// <summary> /// Generate a syslog priority. /// </summary> /// <param name="facility">The syslog facility.</param> /// <param name="severity">The syslog severity.</param> /// <returns>A syslog priority.</returns> public static int GeneratePriority(SyslogFacility facility, SyslogSeverity severity) { return ((int)facility * 8) + (int)severity; } #endregion Public Static Members #region Private Instances Fields /// <summary> /// The facility. The default facility is <see cref="SyslogFacility.User"/>. /// </summary> private SyslogFacility m_facility = SyslogFacility.User; /// <summary> /// The message identity /// </summary> private PatternLayout m_identity; /// <summary> /// Mapping from level object to syslog severity /// </summary> private LevelMapping m_levelMapping = new LevelMapping(); #endregion Private Instances Fields #region LevelSeverity LevelMapping Entry /// <summary> /// A class to act as a mapping between the level that a logging call is made at and /// the syslog severity that is should be logged at. /// </summary> public class LevelSeverity : LevelMappingEntry { private SyslogSeverity m_severity; /// <summary> /// The mapped syslog severity for the specified level /// </summary> public SyslogSeverity Severity { get { return m_severity; } set { m_severity = value; } } } #endregion // LevelSeverity LevelMapping Entry } }
