#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 log4net.Core; using log4net.Appender; using log4net.Util; using log4net.Layout; using System.Text; namespace log4net.Appender { /// /// Logs events to a remote syslog daemon. /// /// /// /// The BSD syslog protocol is used to remotely log to /// a syslog daemon. The syslogd listens for for messages /// on UDP port 514. /// /// /// 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. /// /// /// 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. /// /// /// 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 /// values. The facilities list is predefined /// and cannot be extended. /// /// /// An identifier is specified with each log message. This can be specified /// by setting the property. The identity (also know /// as the tag) must not contain white space. The default value for the /// identity is the application name (from ). /// /// /// Rob Lyon /// Nicko Cadell public class RemoteSyslogAppender : UdpAppender { /// /// Syslog port 514 /// private const int DefaultSyslogPort = 514; #region Enumerations /// /// syslog severities /// /// /// /// The syslog severities. /// /// public enum SyslogSeverity { /// /// system is unusable /// Emergency = 0, /// /// action must be taken immediately /// Alert = 1, /// /// critical conditions /// Critical = 2, /// /// error conditions /// Error = 3, /// /// warning conditions /// Warning = 4, /// /// normal but significant condition /// Notice = 5, /// /// informational /// Informational = 6, /// /// debug-level messages /// Debug = 7 }; /// /// syslog facilities /// /// /// /// The syslog facilities /// /// public enum SyslogFacility { /// /// kernel messages /// Kernel = 0, /// /// random user-level messages /// User = 1, /// /// mail system /// Mail = 2, /// /// system daemons /// Daemons = 3, /// /// security/authorization messages /// Authorization = 4, /// /// messages generated internally by syslogd /// Syslog = 5, /// /// line printer subsystem /// Printer = 6, /// /// network news subsystem /// News = 7, /// /// UUCP subsystem /// Uucp = 8, /// /// clock (cron/at) daemon /// Clock = 9, /// /// security/authorization messages (private) /// Authorization2 = 10, /// /// ftp daemon /// Ftp = 11, /// /// NTP subsystem /// Ntp = 12, /// /// log audit /// Audit = 13, /// /// log alert /// Alert = 14, /// /// clock daemon /// Clock2 = 15, /// /// reserved for local use /// Local0 = 16, /// /// reserved for local use /// Local1 = 17, /// /// reserved for local use /// Local2 = 18, /// /// reserved for local use /// Local3 = 19, /// /// reserved for local use /// Local4 = 20, /// /// reserved for local use /// Local5 = 21, /// /// reserved for local use /// Local6 = 22, /// /// reserved for local use /// Local7 = 23 } #endregion Enumerations #region Public Instance Constructors /// /// Initializes a new instance of the class. /// /// /// This instance of the class is set up to write /// to a remote syslog daemon. /// 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 /// /// Message identity /// /// /// /// An identifier is specified with each log message. This can be specified /// by setting the property. The identity (also know /// as the tag) must not contain white space. The default value for the /// identity is the application name (from ). /// /// public PatternLayout Identity { get { return m_identity; } set { m_identity = value; } } /// /// Syslog facility /// /// /// Set to one of the values. The list of /// facilities is predefined and cannot be extended. The default value /// is . /// public SyslogFacility Facility { get { return m_facility; } set { m_facility = value; } } #endregion Public Instance Properties /// /// Add a mapping of level to severity /// /// The mapping to add /// /// /// Add a mapping to this appender. /// /// public void AddMapping(LevelSeverity mapping) { m_levelMapping.Add(mapping); } #region AppenderSkeleton Implementation /// /// This method is called by the method. /// /// The event to log. /// /// /// Writes the event to a remote syslog daemon. /// /// /// The format of the output will depend on the appender's layout. /// /// protected override void Append(LoggingEvent loggingEvent) { try { // Priority int priority = GeneratePriority(m_facility, GetSeverity(loggingEvent.Level)); // Identity string identity; if (m_identity != null) { identity = m_identity.Format(loggingEvent); } else { identity = loggingEvent.Domain; } // Message. The message goes after the tag/identity string message = RenderLoggingEvent(loggingEvent); Byte[] buffer; int i = 0; char c; StringBuilder builder = new StringBuilder(); while (i < message.Length) { // Clear StringBuilder builder.Length = 0; // Write priority builder.Append('<'); builder.Append(priority); builder.Append('>'); // Write identity builder.Append(identity); builder.Append(": "); for (; i < message.Length; i++) { c = message[i]; // Accept only visible ASCII characters and space. See RFC 3164 section 4.1.3 if (((int)c >= 32) && ((int)c <= 126)) { builder.Append(c); } // If character is newline, break and send the current line else if ((c == '\r') || (c == '\n')) { // Check the next character to handle \r\n or \n\r if ((message.Length > i + 1) && ((message[i + 1] == '\r') || (message[i + 1] == '\n'))) { i++; } i++; break; } } // Grab as a byte array buffer = this.Encoding.GetBytes(builder.ToString()); 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); } } /// /// Initialize the options for this appender /// /// /// /// Initialize the level to syslog severity mappings set on this appender. /// /// public override void ActivateOptions() { base.ActivateOptions(); m_levelMapping.ActivateOptions(); } #endregion AppenderSkeleton Implementation #region Protected Members /// /// Translates a log4net level to a syslog severity. /// /// A log4net level. /// A syslog severity. /// /// /// Translates a log4net level to a syslog severity. /// /// 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 /// /// Generate a syslog priority. /// /// The syslog facility. /// The syslog severity. /// A syslog priority. /// /// /// Generate a syslog priority. /// /// public static int GeneratePriority(SyslogFacility facility, SyslogSeverity severity) { if (facility < SyslogFacility.Kernel || facility > SyslogFacility.Local7) { throw new ArgumentException("SyslogFacility out of range", "facility"); } if (severity < SyslogSeverity.Emergency || severity > SyslogSeverity.Debug) { throw new ArgumentException("SyslogSeverity out of range", "severity"); } unchecked { return ((int)facility * 8) + (int)severity; } } #endregion Public Static Members #region Private Instances Fields /// /// The facility. The default facility is . /// private SyslogFacility m_facility = SyslogFacility.User; /// /// The message identity /// private PatternLayout m_identity; /// /// Mapping from level object to syslog severity /// private LevelMapping m_levelMapping = new LevelMapping(); /// /// Initial buffer size /// private const int c_renderBufferSize = 256; /// /// Maximum buffer size before it is recycled /// private const int c_renderBufferMaxCapacity = 1024; #endregion Private Instances Fields #region LevelSeverity LevelMapping Entry /// /// 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. /// /// /// /// 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. /// /// public class LevelSeverity : LevelMappingEntry { private SyslogSeverity m_severity; /// /// The mapped syslog severity for the specified level /// /// /// /// Required property. /// The mapped syslog severity for the specified level /// /// public SyslogSeverity Severity { get { return m_severity; } set { m_severity = value; } } } #endregion // LevelSeverity LevelMapping Entry } }