| #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.Collections.Generic; |
| using System.Globalization; |
| using System.IO; |
| using System.Runtime.InteropServices; |
| using System.Runtime.Serialization; |
| using System.Security; |
| using System.Security.Principal; |
| using System.Threading; |
| using log4net.Repository; |
| using log4net.Util; |
| |
| namespace log4net.Core; |
| |
| /// <summary> |
| /// Portable data structure used by <see cref="LoggingEvent"/> |
| /// </summary> |
| /// <author>Nicko Cadell</author> |
| [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types")] |
| public struct LoggingEventData |
| { |
| /// <summary> |
| /// The logger name. |
| /// </summary> |
| public string? LoggerName; |
| |
| /// <summary> |
| /// Level of logging event. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// A null level produces varying results depending on the appenders in use. |
| /// In many cases it is equivalent of <see cref="Level.All"/>, other times |
| /// it is mapped to Debug or Info defaults. |
| /// </para> |
| /// <para> |
| /// Level cannot be Serializable because it is a flyweight. |
| /// Due to its special serialization it cannot be declared final either. |
| /// </para> |
| /// </remarks> |
| public Level? Level; |
| |
| /// <summary> |
| /// The application supplied message. |
| /// </summary> |
| public string? Message; |
| |
| /// <summary> |
| /// Gets or sets the name of the thread in which this logging event was generated. |
| /// </summary> |
| public string? ThreadName; |
| |
| /// <summary> |
| /// Gets or sets the UTC time the event was logged. |
| /// </summary> |
| public DateTime TimeStampUtc { get; set; } |
| |
| /// <summary> |
| /// Location information for the caller. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// Location information for the caller. |
| /// </para> |
| /// </remarks> |
| public LocationInfo? LocationInfo; |
| |
| /// <summary> |
| /// String representation of the user |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// String representation of the user's windows name, like DOMAIN\username |
| /// </para> |
| /// </remarks> |
| public string? UserName; |
| |
| /// <summary> |
| /// String representation of the identity. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// String representation of the current thread's principal identity. |
| /// </para> |
| /// </remarks> |
| public string? Identity; |
| |
| /// <summary> |
| /// The string representation of the exception |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// The string representation of the exception |
| /// </para> |
| /// </remarks> |
| public string? ExceptionString; |
| |
| /// <summary> |
| /// String representation of the AppDomain. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// String representation of the AppDomain. |
| /// </para> |
| /// </remarks> |
| public string? Domain; |
| |
| /// <summary> |
| /// Additional event specific properties |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// A logger or an appender may attach additional |
| /// properties to specific events. These properties |
| /// have a string key and an object value. |
| /// </para> |
| /// </remarks> |
| public PropertiesDictionary? Properties; |
| } |
| |
| /// <summary> |
| /// The internal representation of logging events. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// When an affirmative decision is made to log then a |
| /// <see cref="LoggingEvent"/> instance is created. This instance |
| /// is passed around to the different log4net components. |
| /// </para> |
| /// <para> |
| /// This class is of concern to those wishing to extend log4net. |
| /// </para> |
| /// <para> |
| /// Some of the values in instances of <see cref="LoggingEvent"/> |
| /// are considered volatile, that is the values are correct at the |
| /// time the event is delivered to appenders, but will not be consistent |
| /// at any time afterward. If an event is to be stored and then processed |
| /// at a later time these volatile values must be fixed by setting |
| /// <see cref="Fix"/>. There is a performance penalty |
| /// for incurred by calling <see cref="Fix"/> but it |
| /// is essential to maintain data consistency. |
| /// </para> |
| /// </remarks> |
| /// <author>Nicko Cadell</author> |
| /// <author>Gert Driesen</author> |
| /// <author>Douglas de la Torre</author> |
| /// <author>Daniel Cazzulino</author> |
| [Log4NetSerializable] |
| public class LoggingEvent : ILog4NetSerializable |
| { |
| private static readonly Type _declaringType = typeof(LoggingEvent); |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="LoggingEvent" /> class |
| /// from the supplied parameters. |
| /// </summary> |
| /// <param name="callerStackBoundaryDeclaringType">The declaring type of the method that is |
| /// the stack boundary into the logging system for this call.</param> |
| /// <param name="repository">The repository this event is logged in.</param> |
| /// <param name="loggerName">The name of the logger of this event.</param> |
| /// <param name="level"> |
| /// The level of this event. |
| /// A null level produces varying results depending on the appenders in use. |
| /// In many cases it is equivalent of <see cref="Level.All"/>, other times |
| /// it is mapped to Debug or Info defaults. |
| /// </param> |
| /// <param name="message">The message of this event.</param> |
| /// <param name="exception">The exception for this event.</param> |
| /// <remarks> |
| /// <para> |
| /// Except <see cref="TimeStamp"/>, <see cref="Level"/> and <see cref="LoggerName"/>, |
| /// all fields of <see cref="LoggingEvent"/> are lazily filled when actually needed. Set |
| /// <see cref="Fix"/> to cache all data locally to prevent inconsistencies. |
| /// </para> |
| /// <para>This method is called by the log4net framework |
| /// to create a logging event. |
| /// </para> |
| /// </remarks> |
| public LoggingEvent( |
| Type? callerStackBoundaryDeclaringType, |
| ILoggerRepository? repository, |
| string? loggerName, |
| Level? level, |
| object? message, |
| Exception? exception) |
| { |
| _callerStackBoundaryDeclaringType = callerStackBoundaryDeclaringType; |
| MessageObject = message; |
| Repository = repository; |
| ExceptionObject = exception; |
| |
| _data = new() |
| { |
| LoggerName = loggerName, |
| Level = level, |
| // Store the event creation time |
| TimeStampUtc = DateTime.UtcNow |
| }; |
| } |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="LoggingEvent" /> class |
| /// using specific data. |
| /// </summary> |
| /// <param name="callerStackBoundaryDeclaringType">The declaring type of the method that is |
| /// the stack boundary into the logging system for this call.</param> |
| /// <param name="repository">The repository this event is logged in.</param> |
| /// <param name="data">Data used to initialize the logging event.</param> |
| /// <param name="fixedData">The fields in the <paranref name="data"/> struct that have already been fixed.</param> |
| /// <remarks> |
| /// <para> |
| /// This constructor is provided to allow a <see cref="LoggingEvent" /> |
| /// to be created independently of the log4net framework. This can |
| /// be useful if you require a custom serialization scheme. |
| /// </para> |
| /// <para> |
| /// Use the <see cref="GetLoggingEventData(FixFlags)"/> method to obtain an |
| /// instance of the <see cref="LoggingEventData"/> class. |
| /// </para> |
| /// <para> |
| /// The <paramref name="fixedData"/> parameter should be used to specify which fields in the |
| /// <paramref name="data"/> struct have been preset. Fields not specified in the <paramref name="fixedData"/> |
| /// will be captured from the environment if requested or fixed. |
| /// </para> |
| /// </remarks> |
| public LoggingEvent( |
| Type? callerStackBoundaryDeclaringType, |
| ILoggerRepository? repository, |
| LoggingEventData data, |
| FixFlags fixedData) |
| { |
| _callerStackBoundaryDeclaringType = callerStackBoundaryDeclaringType; |
| Repository = repository; |
| |
| _data = data; |
| _fixFlags = fixedData; |
| } |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="LoggingEvent" /> class |
| /// using specific data. |
| /// </summary> |
| /// <param name="callerStackBoundaryDeclaringType">The declaring type of the method that is |
| /// the stack boundary into the logging system for this call.</param> |
| /// <param name="repository">The repository this event is logged in.</param> |
| /// <param name="data">Data used to initialize the logging event.</param> |
| /// <remarks> |
| /// <para> |
| /// This constructor is provided to allow a <see cref="LoggingEvent" /> |
| /// to be created independently of the log4net framework. This can |
| /// be useful if you require a custom serialization scheme. |
| /// </para> |
| /// <para> |
| /// Use the <see cref="GetLoggingEventData(FixFlags)"/> method to obtain an |
| /// instance of the <see cref="LoggingEventData"/> class. |
| /// </para> |
| /// <para> |
| /// This constructor sets this objects <see cref="Fix"/> flags to <see cref="FixFlags.All"/>, |
| /// this assumes that all the data relating to this event is passed in via the <paramref name="data"/> |
| /// parameter and no other data should be captured from the environment. |
| /// </para> |
| /// </remarks> |
| public LoggingEvent( |
| Type? callerStackBoundaryDeclaringType, |
| ILoggerRepository? repository, |
| LoggingEventData data) |
| : this(callerStackBoundaryDeclaringType, repository, data, FixFlags.All) |
| { } |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="LoggingEvent" /> class |
| /// using specific data. |
| /// </summary> |
| /// <param name="data">Data used to initialize the logging event.</param> |
| /// <remarks> |
| /// <para> |
| /// This constructor is provided to allow a <see cref="LoggingEvent" /> |
| /// to be created independently of the log4net framework. This can |
| /// be useful if you require a custom serialization scheme. |
| /// </para> |
| /// <para> |
| /// Use the <see cref="GetLoggingEventData(FixFlags)"/> method to obtain an |
| /// instance of the <see cref="LoggingEventData"/> class. |
| /// </para> |
| /// <para> |
| /// This constructor sets this objects <see cref="Fix"/> flags to <see cref="FixFlags.All"/>, |
| /// this assumes that all the data relating to this event is passed in via the <paramref name="data"/> |
| /// parameter and no other data should be captured from the environment. |
| /// </para> |
| /// </remarks> |
| public LoggingEvent(LoggingEventData data) |
| : this(null, null, data) |
| { } |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="LoggingEvent" /> class. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// This constructor is provided to allow deserialization using System.Text.Json |
| /// or Newtonsoft.Json. |
| /// </para> |
| /// <para> |
| /// Use the <see cref="GetLoggingEventData(FixFlags)"/> method to obtain an |
| /// instance of the <see cref="LoggingEventData"/> class. |
| /// </para> |
| /// <para> |
| /// This constructor sets this objects <see cref="Fix"/> flags to <see cref="FixFlags.None"/>. |
| /// </para> |
| /// </remarks> |
| public LoggingEvent() : this(null, null, new LoggingEventData(), FixFlags.None) |
| { } |
| |
| /// <summary> |
| /// Serialization constructor |
| /// </summary> |
| /// <param name="info">The <see cref="SerializationInfo" /> that holds the serialized object data.</param> |
| /// <param name="context">The <see cref="StreamingContext" /> that contains contextual information about the source or destination.</param> |
| /// <remarks> |
| /// <para> |
| /// Initializes a new instance of the <see cref="LoggingEvent" /> class |
| /// with serialized data. |
| /// </para> |
| /// </remarks> |
| [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter")] |
| protected LoggingEvent(SerializationInfo info, StreamingContext context) |
| { |
| _data = new() |
| { |
| LoggerName = info.EnsureNotNull().GetString("LoggerName"), |
| |
| // Note we are deserializing the whole level object. That is the |
| // name and the value. This value is correct for the source |
| // hierarchy but may not be for the target hierarchy that this |
| // event may be re-logged into. If it is to be re-logged it may |
| // be necessary to re-lookup the level based only on the name. |
| Level = info.GetValue("Level", typeof(Level)) as Level, |
| Message = info.GetString("Message"), |
| ThreadName = info.GetString("ThreadName"), |
| TimeStampUtc = GetTimeStampUtc(info), |
| LocationInfo = info.GetValue("LocationInfo", typeof(LocationInfo)) as LocationInfo, |
| UserName = info.GetString("UserName"), |
| ExceptionString = info.GetString("ExceptionString"), |
| Properties = info.GetValue("Properties", typeof(PropertiesDictionary)) as PropertiesDictionary, |
| Domain = info.GetString("Domain"), |
| Identity = info.GetString("Identity") |
| }; |
| |
| // We have restored all the values of this instance, i.e. all the values are fixed |
| // Set the fix flags otherwise the data values may be overwritten from the current environment. |
| _fixFlags = FixFlags.All; |
| |
| static DateTime GetTimeStampUtc(SerializationInfo info) |
| { |
| // Favor the newer serialization tag 'TimeStampUtc' while supporting the obsolete format from pre-3.0. |
| try |
| { |
| return info.GetDateTime("TimeStampUtc"); |
| } |
| catch (SerializationException) |
| { |
| return info.GetDateTime("TimeStamp").ToUniversalTime(); |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Gets the time when the current process started. |
| /// </summary> |
| /// <value> |
| /// This is the time when this process started. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// The TimeStamp is stored internally in UTC and converted to the local time zone for this computer. |
| /// </para> |
| /// <para> |
| /// Tries to get the start time for the current process. |
| /// Failing that it returns the time of the first call to |
| /// this property. |
| /// </para> |
| /// <para> |
| /// Note that AppDomains may be loaded and unloaded within the |
| /// same process without the process terminating and therefore |
| /// without the process start time being reset. |
| /// </para> |
| /// </remarks> |
| public static DateTime StartTime => SystemInfo.ProcessStartTimeUtc.ToLocalTime(); |
| |
| /// <summary> |
| /// Gets the UTC time when the current process started. |
| /// </summary> |
| /// <value> |
| /// This is the UTC time when this process started. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// Tries to get the start time for the current process. |
| /// Failing that it returns the time of the first call to |
| /// this property. |
| /// </para> |
| /// <para> |
| /// Note that AppDomains may be loaded and unloaded within the |
| /// same process without the process terminating and therefore |
| /// without the process start time being reset. |
| /// </para> |
| /// </remarks> |
| public static DateTime StartTimeUtc => SystemInfo.ProcessStartTimeUtc; |
| |
| /// <summary> |
| /// Gets the <see cref="Level" /> of the logging event. |
| /// A null level produces varying results depending on the appenders in use. |
| /// In many cases it is equivalent of <see cref="Level.All"/>, other times |
| /// it is mapped to Debug or Info defaults. |
| /// </summary> |
| public Level? Level => _data.Level; |
| |
| /// <summary> |
| /// Gets the time of the logging event. |
| /// </summary> |
| /// <value> |
| /// The time of the logging event. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// The TimeStamp is stored in UTC and converted to the local time zone for this computer. |
| /// </para> |
| /// </remarks> |
| public DateTime TimeStamp => _data.TimeStampUtc.ToLocalTime(); |
| |
| /// <summary> |
| /// Gets UTC the time of the logging event. |
| /// </summary> |
| /// <value> |
| /// The UTC time of the logging event. |
| /// </value> |
| public DateTime TimeStampUtc => _data.TimeStampUtc; |
| |
| /// <summary> |
| /// Gets the name of the logger that logged the event. |
| /// </summary> |
| public string? LoggerName => _data.LoggerName; |
| |
| /// <summary> |
| /// Gets the location information for this logging event. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// The collected information is cached for future use. |
| /// </para> |
| /// <para> |
| /// See the <see cref="LocationInfo"/> class for more information on |
| /// supported frameworks and the different behavior in Debug and |
| /// Release builds. |
| /// </para> |
| /// </remarks> |
| public LocationInfo? LocationInformation |
| { |
| get |
| { |
| if (_data.LocationInfo is null && _cacheUpdatable) |
| { |
| _data.LocationInfo = new(_callerStackBoundaryDeclaringType); |
| } |
| |
| return _data.LocationInfo; |
| } |
| } |
| |
| /// <summary> |
| /// Gets the message object used to initialize this event. |
| /// </summary> |
| /// <value> |
| /// The message object used to initialize this event. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// Gets the message object used to initialize this event. |
| /// Note that this event may not have a valid message object. |
| /// If the event is serialized the message object will not |
| /// be transferred. To get the text of the message the |
| /// <see cref="RenderedMessage"/> property must be used |
| /// not this property. |
| /// </para> |
| /// <para> |
| /// If there is no defined message object for this event then |
| /// null will be returned. |
| /// </para> |
| /// </remarks> |
| public object? MessageObject { get; protected set; } |
| |
| /// <summary> |
| /// Gets the exception object used to initialize this event. |
| /// </summary> |
| /// <value> |
| /// The exception object used to initialize this event. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// Gets the exception object used to initialize this event. |
| /// Note that this event may not have a valid exception object. |
| /// If the event is serialized the exception object will not |
| /// be transferred. To get the text of the exception the |
| /// <see cref="GetExceptionString"/> method must be used |
| /// not this property. |
| /// </para> |
| /// <para> |
| /// If there is no defined exception object for this event then |
| /// null will be returned. |
| /// </para> |
| /// </remarks> |
| public Exception? ExceptionObject { get; } |
| |
| /// <summary> |
| /// The <see cref="ILoggerRepository"/> that this event was created in. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// The <see cref="ILoggerRepository"/> that this event was created in. |
| /// </para> |
| /// </remarks> |
| public ILoggerRepository? Repository { get; private set; } |
| |
| /// <summary> |
| /// Ensure that the repository is set. |
| /// </summary> |
| /// <param name="repository">the value for the repository</param> |
| internal void EnsureRepository(ILoggerRepository? repository) |
| { |
| if (repository is not null) |
| { |
| Repository = repository; |
| } |
| } |
| |
| /// <summary> |
| /// Gets the message, rendered through the <see cref="ILoggerRepository.RendererMap" />. |
| /// </summary> |
| /// <value> |
| /// The message rendered through the <see cref="ILoggerRepository.RendererMap" />. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// The collected information is cached for future use. |
| /// </para> |
| /// </remarks> |
| public virtual string? RenderedMessage |
| { |
| get |
| { |
| if (_data.Message is null && _cacheUpdatable) |
| { |
| if (MessageObject is null) |
| { |
| _data.Message = string.Empty; |
| } |
| else if (MessageObject is string s) |
| { |
| _data.Message = s; |
| } |
| else if (Repository is not null) |
| { |
| _data.Message = Repository.RendererMap.FindAndRender(MessageObject); |
| } |
| else |
| { |
| // Very last resort |
| _data.Message = MessageObject.ToString(); |
| } |
| } |
| |
| return _data.Message; |
| } |
| } |
| |
| /// <summary> |
| /// Write the rendered message to a TextWriter |
| /// </summary> |
| /// <param name="writer">the writer to write the message to</param> |
| /// <remarks> |
| /// <para> |
| /// Unlike the <see cref="RenderedMessage"/> property this method |
| /// does store the message data in the internal cache. Therefore |
| /// if called only once this method should be faster than the |
| /// <see cref="RenderedMessage"/> property, however if the message is |
| /// to be accessed multiple times then the property will be more efficient. |
| /// </para> |
| /// </remarks> |
| public virtual void WriteRenderedMessage(TextWriter writer) |
| { |
| writer.EnsureNotNull(); |
| if (_data.Message is not null) |
| { |
| writer.Write(_data.Message); |
| } |
| else |
| { |
| if (MessageObject is not null) |
| { |
| if (MessageObject is string s) |
| { |
| writer.Write(s); |
| } |
| else if (Repository is not null) |
| { |
| Repository.RendererMap.FindAndRender(MessageObject, writer); |
| } |
| else |
| { |
| // Very last resort |
| writer.Write(MessageObject.ToString()); |
| } |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Gets the name of the current thread. |
| /// </summary> |
| /// <value> |
| /// The name of the current thread, or the thread ID when |
| /// the name is not available. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// The collected information is cached for future use. |
| /// </para> |
| /// </remarks> |
| public string? ThreadName |
| { |
| get |
| { |
| if (_data.ThreadName is null && _cacheUpdatable) |
| { |
| _data.ThreadName = ReviseThreadName(Thread.CurrentThread.Name); |
| } |
| |
| return _data.ThreadName; |
| } |
| } |
| |
| /// <summary> |
| /// Returns a 'meaningful' name for the thread (or its Id) |
| /// </summary> |
| /// <param name="threadName">Name</param> |
| /// <returns>Meaningful name</returns> |
| private static string ReviseThreadName(string? threadName) |
| { |
| // '.NET ThreadPool Worker' appears as a default thread name in the .NET 6-7 thread pool. |
| // '.NET TP Worker' is the default thread name in the .NET 8+ thread pool. |
| // '.NET Long Running Task' is used for long running tasks |
| // Prefer the numeric thread ID instead. |
| if (threadName is string { Length: > 0 } name |
| && !name.StartsWith(".NET ", StringComparison.Ordinal)) |
| { |
| return name; |
| } |
| // The thread name is not available or unsuitable, therefore we use the ManagedThreadId. |
| try |
| { |
| return SystemInfo.CurrentThreadId.ToString(NumberFormatInfo.InvariantInfo); |
| } |
| catch (SecurityException) |
| { |
| // This security exception will occur if the caller does not have |
| // some undefined set of SecurityPermission flags. |
| LogLog.Debug(_declaringType, |
| "Security exception while trying to get current thread ID. Error Ignored. Empty thread name."); |
| |
| // As a last resort use the hash code of the Thread object |
| return Thread.CurrentThread.GetHashCode().ToString(CultureInfo.InvariantCulture); |
| } |
| } |
| |
| /// <summary> |
| /// Gets the name of the current user. |
| /// </summary> |
| /// <value> |
| /// The name of the current user, or <c>NOT AVAILABLE</c> when the |
| /// underlying runtime has no support for retrieving the name of the |
| /// current user. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// On Windows it calls <c>WindowsIdentity.GetCurrent().Name</c> to get the name of |
| /// the current windows user. On other OSes it calls Environment.UserName. |
| /// </para> |
| /// <para> |
| /// To improve performance, we could cache the string representation of |
| /// the name, and reuse that as long as the identity stayed constant. |
| /// Once the identity changed, we would need to re-assign and re-render |
| /// the string. |
| /// </para> |
| /// <para> |
| /// However, the <c>WindowsIdentity.GetCurrent()</c> call seems to |
| /// return different objects every time, so the current implementation |
| /// doesn't do this type of caching. |
| /// </para> |
| /// <para> |
| /// Timing for these operations: |
| /// </para> |
| /// <list type="table"> |
| /// <listheader> |
| /// <term>Method</term> |
| /// <description>Results</description> |
| /// </listheader> |
| /// <item> |
| /// <term><c>WindowsIdentity.GetCurrent()</c></term> |
| /// <description>10000 loops, 00:00:00.2031250 seconds</description> |
| /// </item> |
| /// <item> |
| /// <term><c>WindowsIdentity.GetCurrent().Name</c></term> |
| /// <description>10000 loops, 00:00:08.0468750 seconds</description> |
| /// </item> |
| /// </list> |
| /// <para> |
| /// This means we could speed things up almost 40 times by caching the |
| /// value of the <c>WindowsIdentity.GetCurrent().Name</c> property, since |
| /// this takes (8.04-0.20) = 7.84375 seconds. |
| /// </para> |
| /// </remarks> |
| public string UserName => |
| _data.UserName ??= TryGetCurrentUserName() ?? SystemInfo.NotAvailableText; |
| |
| private string? TryGetCurrentUserName() |
| { |
| try |
| { |
| if (_platformDoesNotSupportWindowsIdentity) |
| { |
| // we've already received one PlatformNotSupportedException or null from TryReadWindowsIdentityUserName |
| // and it's highly unlikely that will change |
| return Environment.UserName; |
| } |
| |
| if (_cachedWindowsIdentityUserName is not null) |
| { |
| return _cachedWindowsIdentityUserName; |
| } |
| if (TryReadWindowsIdentityUserName() is string userName) |
| { |
| _cachedWindowsIdentityUserName = userName; |
| return _cachedWindowsIdentityUserName; |
| } |
| _platformDoesNotSupportWindowsIdentity = true; |
| return Environment.UserName; |
| } |
| catch (PlatformNotSupportedException) |
| { |
| _platformDoesNotSupportWindowsIdentity = true; |
| return Environment.UserName; |
| } |
| catch (SecurityException) |
| { |
| // This security exception will occur if the caller does not have |
| // some undefined set of SecurityPermission flags. |
| LogLog.Debug( |
| _declaringType, |
| "Security exception while trying to get current windows identity. Error Ignored." |
| ); |
| return Environment.UserName; |
| } |
| catch (Exception e) when (!e.IsFatal()) |
| { |
| return null; |
| } |
| } |
| |
| private string? _cachedWindowsIdentityUserName; |
| |
| /// <returns> |
| /// On Windows: UserName in case of success, empty string for unexpected null in identity or Name |
| /// <para/> |
| /// On other OSes: null |
| /// </returns> |
| /// <exception cref="PlatformNotSupportedException">Thrown on non-Windows platforms on net462</exception> |
| private static string? TryReadWindowsIdentityUserName() |
| { |
| // According to docs RuntimeInformation.IsOSPlatform is supported from netstandard1.1, |
| // but it's erroring in runtime on < net471 |
| #if NET471_OR_GREATER || NETSTANDARD2_0_OR_GREATER |
| if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
| { |
| return null; |
| } |
| #endif |
| using WindowsIdentity identity = WindowsIdentity.GetCurrent(); |
| return identity?.Name ?? string.Empty; |
| } |
| |
| private static bool _platformDoesNotSupportWindowsIdentity; |
| |
| /// <summary> |
| /// Gets the identity of the current thread principal. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// Calls <c>System.Threading.Thread.CurrentPrincipal.Identity.Name</c> to get |
| /// the name of the current thread principal. |
| /// </para> |
| /// </remarks> |
| public string? Identity |
| { |
| get |
| { |
| if (_data.Identity is null && _cacheUpdatable) |
| { |
| try |
| { |
| if (Thread.CurrentPrincipal?.Identity?.Name is string name) |
| { |
| _data.Identity = name; |
| } |
| else |
| { |
| _data.Identity = string.Empty; |
| } |
| } |
| catch (ObjectDisposedException) |
| { |
| // This exception will occur if Thread.CurrentPrincipal.Identity is not null but |
| // the getter of the property Name tries to access disposed objects. |
| // Seen to happen on IIS 7 or greater with windows authentication. |
| LogLog.Debug(_declaringType, |
| "Object disposed exception while trying to get current thread principal. Error Ignored. Empty identity name."); |
| |
| _data.Identity = string.Empty; |
| } |
| catch (SecurityException) |
| { |
| // This security exception will occur if the caller does not have |
| // some undefined set of SecurityPermission flags. |
| LogLog.Debug(_declaringType, |
| "Security exception while trying to get current thread principal. Error Ignored. Empty identity name."); |
| |
| _data.Identity = string.Empty; |
| } |
| } |
| |
| return _data.Identity; |
| } |
| } |
| |
| /// <summary> |
| /// Gets the AppDomain friendly name. |
| /// </summary> |
| public string? Domain |
| { |
| get |
| { |
| if (_data.Domain is null && _cacheUpdatable) |
| { |
| _data.Domain = SystemInfo.ApplicationFriendlyName; |
| } |
| |
| return _data.Domain; |
| } |
| } |
| |
| /// <summary> |
| /// Additional event specific properties. |
| /// </summary> |
| /// <value> |
| /// Additional event specific properties. |
| /// </value> |
| /// <remarks> |
| /// <para> |
| /// A logger or an appender may attach additional |
| /// properties to specific events. These properties |
| /// have a string key and an object value. |
| /// </para> |
| /// <para> |
| /// This property is for events that have been added directly to |
| /// this event. The aggregate properties (which include these |
| /// event properties) can be retrieved using <see cref="LookupProperty"/> |
| /// and <see cref="GetProperties"/>. |
| /// </para> |
| /// <para> |
| /// Once the properties have been fixed <see cref="Fix"/> this property |
| /// returns the combined cached properties. This ensures that updates to |
| /// this property are always reflected in the underlying storage. When |
| /// returning the combined properties there may be more keys in the |
| /// Dictionary than expected. |
| /// </para> |
| /// </remarks> |
| [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1721:Property names should not match get methods")] |
| public PropertiesDictionary Properties |
| { |
| get |
| { |
| // If we have cached properties then return that otherwise changes will be lost |
| if (_data.Properties is not null) |
| { |
| return _data.Properties; |
| } |
| |
| _eventProperties ??= []; |
| return _eventProperties; |
| } |
| } |
| |
| /// <summary> |
| /// Gets the fixed fields in this event, or on set, fixes fields specified in the value. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// Fields will not be fixed if they have previously been fixed. |
| /// It is not possible to 'unfix' a field. |
| /// </para> |
| /// </remarks> |
| public FixFlags Fix |
| { |
| get => _fixFlags; |
| set => FixVolatileData(value); |
| } |
| |
| /// <summary> |
| /// Serializes this object into the <see cref="SerializationInfo" /> provided. |
| /// </summary> |
| /// <param name="info">The <see cref="SerializationInfo" /> to populate with data.</param> |
| /// <param name="context">The destination for this serialization.</param> |
| /// <remarks> |
| /// <para> |
| /// The data in this event must be fixed before it can be serialized. |
| /// </para> |
| /// <para> |
| /// The <see cref="Fix"/> property must be set during the |
| /// <see cref="Appender.IAppender.DoAppend"/> method call if this event |
| /// is to be used outside that method. |
| /// </para> |
| /// </remarks> |
| [SecurityCritical] |
| [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, |
| SerializationFormatter = true)] |
| public virtual void GetObjectData(SerializationInfo info, StreamingContext context) |
| { |
| info.EnsureNotNull(); |
| // The caller must set Fix before this object can be serialized. |
| info.AddValue("LoggerName", _data.LoggerName); |
| info.AddValue("Level", _data.Level); |
| info.AddValue("Message", _data.Message); |
| info.AddValue("ThreadName", _data.ThreadName); |
| |
| // Serialize UTC->local time for backward compatibility with obsolete 'TimeStamp' property. |
| info.AddValue("TimeStamp", _data.TimeStampUtc.ToLocalTime()); |
| |
| // Also add the UTC time under its own serialization tag. |
| info.AddValue("TimeStampUtc", _data.TimeStampUtc); |
| |
| info.AddValue("LocationInfo", _data.LocationInfo); |
| info.AddValue("UserName", _data.UserName); |
| info.AddValue("ExceptionString", _data.ExceptionString); |
| info.AddValue("Properties", _data.Properties); |
| info.AddValue("Domain", _data.Domain); |
| info.AddValue("Identity", _data.Identity); |
| } |
| |
| /// <summary> |
| /// Gets the portable data for this <see cref="LoggingEvent" />. |
| /// </summary> |
| /// <returns>The <see cref="LoggingEventData"/> for this event.</returns> |
| /// <remarks> |
| /// <para> |
| /// A new <see cref="LoggingEvent"/> can be constructed using a |
| /// <see cref="LoggingEventData"/> instance. |
| /// </para> |
| /// <para> |
| /// Does a <see cref="FixFlags.Partial"/> fix of the data |
| /// in the logging event before returning the event data. |
| /// </para> |
| /// </remarks> |
| public LoggingEventData GetLoggingEventData() |
| => GetLoggingEventData(FixFlags.Partial); |
| |
| /// <summary> |
| /// Gets the portable data for this <see cref="LoggingEvent" />. |
| /// </summary> |
| /// <param name="fixFlags">The set of data to ensure is fixed in the LoggingEventData</param> |
| /// <returns>The <see cref="LoggingEventData"/> for this event.</returns> |
| /// <remarks> |
| /// <para> |
| /// A new <see cref="LoggingEvent"/> can be constructed using a |
| /// <see cref="LoggingEventData"/> instance. |
| /// </para> |
| /// </remarks> |
| public LoggingEventData GetLoggingEventData(FixFlags fixFlags) |
| { |
| Fix = fixFlags; |
| return _data; |
| } |
| |
| /// <summary> |
| /// Returns this event's exception's rendered using the |
| /// <see cref="ILoggerRepository.RendererMap" />. |
| /// </summary> |
| /// <returns> |
| /// This event's exception's rendered using the <see cref="ILoggerRepository.RendererMap" />. |
| /// </returns> |
| /// <remarks> |
| /// <para> |
| /// Returns this event's exception's rendered using the |
| /// <see cref="ILoggerRepository.RendererMap" />. |
| /// </para> |
| /// </remarks> |
| public string? GetExceptionString() |
| { |
| if (_data.ExceptionString is null && _cacheUpdatable) |
| { |
| if (ExceptionObject is not null) |
| { |
| if (Repository is not null) |
| { |
| // Render exception using the repositories renderer map |
| _data.ExceptionString = Repository.RendererMap.FindAndRender(ExceptionObject); |
| } |
| else |
| { |
| // Very last resort |
| _data.ExceptionString = ExceptionObject.ToString(); |
| } |
| } |
| else |
| { |
| _data.ExceptionString = string.Empty; |
| } |
| } |
| |
| return _data.ExceptionString; |
| } |
| |
| /// <summary> |
| /// Fix the fields specified by the <see cref="FixFlags"/> parameter |
| /// </summary> |
| /// <param name="flags">the fields to fix</param> |
| /// <remarks> |
| /// <para> |
| /// Only fields specified in the <paramref name="flags"/> will be fixed. |
| /// Fields will not be fixed if they have previously been fixed. |
| /// It is not possible to 'unfix' a field. |
| /// </para> |
| /// </remarks> |
| protected virtual void FixVolatileData(FixFlags flags) |
| { |
| // Unlock the cache so that new values can be stored |
| // This may not be ideal if we are no longer in the correct context |
| // and someone calls fix. |
| _cacheUpdatable = true; |
| |
| // determine the flags that we are actually fixing |
| FixFlags updateFlags = (flags ^ _fixFlags) & flags; |
| |
| if (updateFlags > 0) |
| { |
| if ((updateFlags & FixFlags.Message) != 0) |
| { |
| // Force the message to be rendered |
| _ = RenderedMessage; |
| |
| _fixFlags |= FixFlags.Message; |
| } |
| |
| if ((updateFlags & FixFlags.ThreadName) != 0) |
| { |
| // Grab the thread name |
| _ = ThreadName; |
| |
| _fixFlags |= FixFlags.ThreadName; |
| } |
| |
| if ((updateFlags & FixFlags.LocationInfo) != 0) |
| { |
| // Force the location information to be loaded |
| _ = LocationInformation; |
| |
| _fixFlags |= FixFlags.LocationInfo; |
| } |
| |
| if ((updateFlags & FixFlags.UserName) != 0) |
| { |
| // Grab the user name |
| _ = UserName; |
| |
| _fixFlags |= FixFlags.UserName; |
| } |
| |
| if ((updateFlags & FixFlags.Domain) != 0) |
| { |
| // Grab the domain name |
| _ = Domain; |
| |
| _fixFlags |= FixFlags.Domain; |
| } |
| |
| if ((updateFlags & FixFlags.Identity) != 0) |
| { |
| // Grab the identity |
| _ = Identity; |
| |
| _fixFlags |= FixFlags.Identity; |
| } |
| |
| if ((updateFlags & FixFlags.Exception) != 0) |
| { |
| // Force the exception text to be loaded |
| _ = GetExceptionString(); |
| |
| _fixFlags |= FixFlags.Exception; |
| } |
| |
| if ((updateFlags & FixFlags.Properties) != 0) |
| { |
| CacheProperties(); |
| |
| _fixFlags |= FixFlags.Properties; |
| } |
| } |
| |
| // Finally lock everything we've cached. |
| _cacheUpdatable = false; |
| } |
| |
| private void CreateCompositeProperties() |
| { |
| CompositeProperties compositeProperties = new(); |
| |
| if (_eventProperties is not null) |
| { |
| compositeProperties.Add(_eventProperties); |
| } |
| if (LogicalThreadContext.Properties.GetProperties(false) is PropertiesDictionary logicalThreadProperties) |
| { |
| compositeProperties.Add(logicalThreadProperties); |
| } |
| if (ThreadContext.Properties.GetProperties(false) is PropertiesDictionary threadProperties) |
| { |
| compositeProperties.Add(threadProperties); |
| } |
| |
| // TODO: Add Repository Properties |
| |
| // event properties |
| bool shouldFixUserName = (_fixFlags & FixFlags.UserName) != 0; |
| bool shouldFixIdentity = (_fixFlags & FixFlags.Identity) != 0; |
| if (shouldFixIdentity || shouldFixUserName) |
| { |
| PropertiesDictionary eventProperties = new(); |
| if (shouldFixUserName) |
| { |
| eventProperties[UserNameProperty] = UserName; |
| } |
| |
| if (shouldFixIdentity) |
| { |
| eventProperties[IdentityProperty] = Identity; |
| } |
| |
| compositeProperties.Add(eventProperties); |
| } |
| |
| compositeProperties.Add(GlobalContext.Properties.GetReadOnlyProperties()); |
| _compositeProperties = compositeProperties; |
| } |
| |
| private void CacheProperties() |
| { |
| if (_data.Properties is null && _cacheUpdatable) |
| { |
| if (_compositeProperties is null) |
| { |
| CreateCompositeProperties(); |
| } |
| |
| PropertiesDictionary flattenedProperties = _compositeProperties!.Flatten(); |
| |
| PropertiesDictionary fixedProperties = new(); |
| |
| // Validate properties |
| foreach (KeyValuePair<string, object?> entry in flattenedProperties) |
| { |
| object? val = entry.Value; |
| |
| // Fix any IFixingRequired objects |
| if (entry.Value is IFixingRequired fixingRequired) |
| { |
| val = fixingRequired.GetFixedObject(); |
| } |
| |
| // Strip keys with null values |
| if (val is not null) |
| { |
| fixedProperties[entry.Key] = val; |
| } |
| } |
| |
| _data.Properties = fixedProperties; |
| } |
| } |
| |
| /// <summary> |
| /// Looks up a composite property in this event |
| /// </summary> |
| /// <param name="key">the key for the property to lookup</param> |
| /// <returns>the value for the property</returns> |
| /// <remarks> |
| /// <para> |
| /// This event has composite properties that combine properties from |
| /// several different contexts in the following order: |
| /// <list type="definition"> |
| /// <item> |
| /// <term>this event's properties</term> |
| /// <description> |
| /// This event has <see cref="Properties"/> that can be set. These |
| /// properties are specific to this event only. |
| /// </description> |
| /// </item> |
| /// <item> |
| /// <term>the thread properties</term> |
| /// <description> |
| /// The <see cref="ThreadContext.Properties"/> that are set on the current |
| /// thread. These properties are shared by all events logged on this thread. |
| /// </description> |
| /// </item> |
| /// <item> |
| /// <term>the global properties</term> |
| /// <description> |
| /// The <see cref="GlobalContext.Properties"/> that are set globally. These |
| /// properties are shared by all the threads in the AppDomain. |
| /// </description> |
| /// </item> |
| /// </list> |
| /// </para> |
| /// </remarks> |
| public object? LookupProperty(string key) |
| { |
| if (_data.Properties is not null) |
| { |
| return _data.Properties[key]; |
| } |
| |
| if (_compositeProperties is null) |
| { |
| CreateCompositeProperties(); |
| } |
| |
| return _compositeProperties![key]; |
| } |
| |
| /// <summary> |
| /// Get all the composite properties in this event |
| /// </summary> |
| /// <returns>the <see cref="PropertiesDictionary"/> containing all the properties</returns> |
| /// <remarks> |
| /// <para> |
| /// See <see cref="LookupProperty"/> for details of the composite properties |
| /// stored by the event. |
| /// </para> |
| /// <para> |
| /// This method returns a single <see cref="PropertiesDictionary"/> containing all the |
| /// properties defined for this event. |
| /// </para> |
| /// </remarks> |
| public PropertiesDictionary GetProperties() |
| { |
| if (_data.Properties is not null) |
| { |
| return _data.Properties; |
| } |
| |
| if (_compositeProperties is null) |
| { |
| CreateCompositeProperties(); |
| } |
| |
| return _compositeProperties!.Flatten(); |
| } |
| |
| /// <summary> |
| /// The internal logging event data. |
| /// </summary> |
| private LoggingEventData _data; |
| |
| /// <summary> |
| /// Location information for the caller. |
| /// </summary> |
| public LocationInfo? LocationInfo => _data.LocationInfo; |
| |
| /// <summary> |
| /// The internal logging event data. |
| /// </summary> |
| private CompositeProperties? _compositeProperties; |
| |
| /// <summary> |
| /// The internal logging event data. |
| /// </summary> |
| private PropertiesDictionary? _eventProperties; |
| |
| /// <summary> |
| /// The fully qualified Type of the calling |
| /// logger class in the stack frame (i.e. the declaring type of the method). |
| /// </summary> |
| private readonly Type? _callerStackBoundaryDeclaringType; |
| |
| /// <summary> |
| /// The fix state for this event |
| /// </summary> |
| /// <remarks> |
| /// These flags indicate which fields have been fixed. |
| /// Not serialized. |
| /// </remarks> |
| private FixFlags _fixFlags = FixFlags.None; |
| |
| /// <summary> |
| /// Indicated that the internal cache is updateable (ie not fixed) |
| /// </summary> |
| /// <remarks> |
| /// This is a separate flag to fixFlags as it allows incremental fixing and simpler |
| /// changes in the caching strategy. |
| /// </remarks> |
| private bool _cacheUpdatable = true; |
| |
| /// <summary> |
| /// The key into the Properties map for the host name value. |
| /// </summary> |
| public const string HostNameProperty = "log4net:HostName"; |
| |
| /// <summary> |
| /// The key into the Properties map for the thread identity value. |
| /// </summary> |
| public const string IdentityProperty = "log4net:Identity"; |
| |
| /// <summary> |
| /// The key into the Properties map for the user name value. |
| /// </summary> |
| public const string UserNameProperty = "log4net:UserName"; |
| } |