#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 { /// <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> /// <remarks> /// <para> /// The syslog severities. /// </para> /// </remarks> 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> /// <remarks> /// <para> /// The syslog facilities /// </para> /// </remarks> 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> /// <remarks> /// <para> /// Add a <see cref="LevelSeverity"/> mapping to this appender. /// </para> /// </remarks> public void AddMapping(LevelSeverity mapping) { m_levelMapping.Add(mapping); } #region AppenderSkeleton Implementation /// <summary> /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent)"/> 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 { // 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()); #if NETSTANDARD1_3 Client.SendAsync(buffer, buffer.Length, RemoteEndPoint).Wait(); #else this.Client.Send(buffer, buffer.Length, this.RemoteEndPoint); #endif } } catch (Exception e) { ErrorHandler.Error( "Unable to send logging event to remote syslog " + this.RemoteAddress.ToString() + " on port " + this.RemotePort + ".", e, ErrorCode.WriteFailure); } } /// <summary> /// Initialize the options for this appender /// </summary> /// <remarks> /// <para> /// Initialize 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> /// <remarks> /// <para> /// Translates a log4net level to a syslog severity. /// </para> /// </remarks> 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> /// <remarks> /// <para> /// Generate a syslog priority. /// </para> /// </remarks> 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 /// <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(); /// <summary> /// Initial buffer size /// </summary> private const int c_renderBufferSize = 256; /// <summary> /// Maximum buffer size before it is recycled /// </summary> private const int c_renderBufferMaxCapacity = 1024; #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> /// <remarks> /// <para> /// 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. /// </para> /// </remarks> public class LevelSeverity : LevelMappingEntry { private SyslogSeverity m_severity; /// <summary> /// The mapped syslog severity for the specified level /// </summary> /// <remarks> /// <para> /// Required property. /// The mapped syslog severity for the specified level /// </para> /// </remarks> public SyslogSeverity Severity { get { return m_severity; } set { m_severity = value; } } } #endregion // LevelSeverity LevelMapping Entry } }