src/log4net/Appender/LocalSyslogAppender.cs (132 lines of code) (raw):

#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.Runtime.InteropServices; using log4net.Core; using log4net.Util; 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 { /// <summary> /// syslog severities /// </summary> /// <remarks> /// <para> /// The log4net Level maps to a syslog severity using the /// <see cref="LocalSyslogAppender.AddMapping"/> method and the <see cref="LevelSeverity"/> /// class. The severity is set on <see cref="LevelSeverity.Severity"/>. /// </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 facility defines which subsystem the logging comes from. /// This is set on the <see cref="Facility"/> property. /// </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 } /// <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 known /// 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; set; } /// <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; set; } = SyslogFacility.User; /// <summary> /// Add a mapping of level to severity /// </summary> /// <param name="mapping">The mapping to add</param> /// <remarks> /// <para> /// Adds a <see cref="LevelSeverity"/> to this appender. /// </para> /// </remarks> public void AddMapping(LevelSeverity mapping) { _levelMapping.Add(mapping); } /// <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> [System.Security.SecuritySafeCritical] public override void ActivateOptions() { base.ActivateOptions(); _levelMapping.ActivateOptions(); // Set to app name by default string? identString = Identity ?? 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. _handleToIdentity = Marshal.StringToHGlobalAnsi(identString); // open syslog NativeMethods.openlog(_handleToIdentity, 1, Facility); } /// <summary> /// This method is called by the <see cref="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> [System.Security.SecuritySafeCritical] [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode = true)] protected override void Append(LoggingEvent loggingEvent) { int priority = GeneratePriority(Facility, GetSeverity(loggingEvent.EnsureNotNull().Level)); string message = RenderLoggingEvent(loggingEvent); // Call the local libc syslog method // The second argument is a printf style format string NativeMethods.syslog(priority, "%s", message); } /// <summary> /// Close the syslog when the appender is closed /// </summary> /// <remarks> /// <para> /// Close the syslog when the appender is closed /// </para> /// </remarks> [System.Security.SecuritySafeCritical] protected override void OnClose() { base.OnClose(); try { // close syslog NativeMethods.closelog(); } catch (DllNotFoundException) { // Ignore dll not found at this point } if (_handleToIdentity != IntPtr.Zero) { // free global ident Marshal.FreeHGlobal(_handleToIdentity); } } /// <summary> /// This appender requires a <see cref="AppenderSkeleton.Layout"/> to be set. /// </summary> protected override bool RequiresLayout => true; /// <summary> /// Translates a log4net level to a syslog severity. /// </summary> /// <param name="level">A log4net level.</param> /// <returns>A syslog severity.</returns> protected virtual SyslogSeverity GetSeverity(Level? level) { if (_levelMapping.Lookup(level) is LevelSeverity levelSeverity) { return levelSeverity.Severity; } // // Fall back to sensible default values // if (level is null) { // Default setting return SyslogSeverity.Debug; } 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; } /// <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> private static int GeneratePriority(SyslogFacility facility, SyslogSeverity severity) => ((int)facility * 8) + (int)severity; /// <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 _handleToIdentity = IntPtr.Zero; /// <summary> /// Mapping from level object to syslog severity /// </summary> private readonly LevelMapping _levelMapping = new(); /// <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 { /// <summary> /// The mapped syslog severity for the specified level /// </summary> public SyslogSeverity Severity { get; set; } } }