//****************************************************************************************************** // Logger.cs - Gbtc // // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the Eclipse Public License -v 1.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.opensource.org/licenses/eclipse-1.0.php // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 10/24/2016 - Steven E. Chisholm // Generated original version of source code. // // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Security; using System.Security.Permissions; using System.Threading; using GSF.Threading; namespace GSF.Diagnostics { /// /// Manages the collection and reporting of logging information in a system. /// public static class Logger { private enum SuppressionMode { None = 0, FirstChanceExceptionOnly = 1, AllMessages = 2, } private static class ThreadLocalThreadStack { [ThreadStatic] private static ThreadStack s_localValue; /// /// Gets the item for the current thread. /// Note: No exchange compare is needed since /// is local only to the current thread. /// public static ThreadStack Value => s_localValue ?? (s_localValue = new ThreadStack()); } /// /// This information is maintained in a ThreadLocal variable and is about /// messages and log suppression applied at higher levels of the calling stack. /// private class ThreadStack { public int PreviousFirstChanceExceptionSequenceNumber; private List m_threadStackDetails = new List(); private LogStackMessages m_stackMessageCache; private List m_logMessageSuppressionStack = new List(); public bool ShouldSuppressLogMessages => m_logMessageSuppressionStack.Count > 0 && m_logMessageSuppressionStack[m_logMessageSuppressionStack.Count - 1] >= SuppressionMode.AllMessages; public bool ShouldSuppressFirstChanceLogMessages => m_logMessageSuppressionStack.Count > 0 && m_logMessageSuppressionStack[m_logMessageSuppressionStack.Count - 1] >= SuppressionMode.FirstChanceExceptionOnly; public LogStackMessages GetStackMessages() { if (m_stackMessageCache == null) { if (m_threadStackDetails.Count == 0) { m_stackMessageCache = LogStackMessages.Empty; } else { m_stackMessageCache = new LogStackMessages(m_threadStackDetails); } } return m_stackMessageCache; } public StackDisposal AppendStackMessages(LogStackMessages messages) { m_stackMessageCache = null; m_threadStackDetails.Add(messages); int depth = m_threadStackDetails.Count; if (depth >= s_stackDisposalStackMessages.Length) { GrowStackDisposal(depth + 1); } return s_stackDisposalStackMessages[depth]; } public StackDisposal SuppressLogMessages(SuppressionMode suppressionMode) { m_logMessageSuppressionStack.Add(suppressionMode); int depth = m_logMessageSuppressionStack.Count; if (depth >= s_stackDisposalSuppressionFlags.Length) { GrowStackDisposal(depth + 1); } return s_stackDisposalSuppressionFlags[depth]; } public void RemoveStackMessage(int depth) { while (m_threadStackDetails.Count >= depth) { m_threadStackDetails.RemoveAt(m_threadStackDetails.Count - 1); } m_stackMessageCache = null; } public void RemoveSuppression(int depth) { while (m_logMessageSuppressionStack.Count >= depth) { m_logMessageSuppressionStack.RemoveAt(m_logMessageSuppressionStack.Count - 1); } } } private static LoggerInternal s_logger; /// /// The default console based log subscriber. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly LogSubscriptionConsole Console; /// /// The default file based log writer. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly LogSubscriptionFileWriter FileWriter; private static readonly LogPublisher Log; private static readonly LogEventPublisher EventFirstChanceException; private static readonly LogEventPublisher EventAppDomainException; private static readonly LogEventPublisher EventSwallowedException; private static StackDisposal[] s_stackDisposalStackMessages; private static StackDisposal[] s_stackDisposalSuppressionFlags; private static readonly object SyncRoot = new object(); static Logger() { //Initializes the empty object of StackTraceDetails LogStackTrace.Initialize(); LogStackMessages.Initialize(); GrowStackDisposal(1); s_logger = new LoggerInternal(out s_logger); Console = new LogSubscriptionConsole(); FileWriter = new LogSubscriptionFileWriter(1000); AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; Log = CreatePublisher(typeof(Logger), MessageClass.Component); Log.InitialStackTrace = LogStackTrace.Empty; EventFirstChanceException = Log.RegisterEvent(MessageLevel.NA, MessageFlags.SystemHealth, "First Chance App Domain Exception", 30, MessageRate.PerSecond(30), 1000); EventAppDomainException = Log.RegisterEvent(MessageLevel.Critical, MessageFlags.SystemHealth, "Unhandled App Domain Exception"); EventSwallowedException = Log.RegisterEvent(MessageLevel.Debug, MessageFlags.None, "Exception was Swallowed", 30, MessageRate.PerSecond(30), 1000); ShutdownHandler.Initialize(); } /// /// Ensures that the logger has been initialized. /// internal static void Initialize() { //Handled in the static constructor. } /// /// Ensures that the logger is properly shutdown. /// This is called from ShutdownHandler. /// internal static void Shutdown() { try { Log.Publish(MessageLevel.Critical, MessageFlags.SystemHealth, "Logger is shutting down."); s_logger.Dispose(); Console.Verbose = VerboseLevel.None; FileWriter.Dispose(); //Cannot raise log messages here since the logger is now shutdown. } catch (Exception) { } } /// /// Gets the sequence number of the most recent First Chance Exception log /// internal static int PreviousFirstChanceExceptionSequenceNumber => ThreadLocalThreadStack.Value.PreviousFirstChanceExceptionSequenceNumber; /// /// Gets if Log Messages should be suppressed. /// public static bool ShouldSuppressLogMessages => ThreadLocalThreadStack.Value.ShouldSuppressLogMessages; /// /// Gets if First Chance Exception Log Messages should be suppressed. /// public static bool ShouldSuppressFirstChanceLogMessages => ThreadLocalThreadStack.Value.ShouldSuppressFirstChanceLogMessages; private static void CurrentDomain_FirstChanceException(object sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e) { if ((Thread.CurrentThread.ThreadState & (ThreadState.AbortRequested | ThreadState.Aborted)) != 0) { return; } if (ShouldSuppressFirstChanceLogMessages) return; using (SuppressFirstChanceExceptionLogMessages()) { try { EventFirstChanceException.Publish(null, null, e.Exception); } catch (Exception) { //swallow any exceptions. } ThreadLocalThreadStack.Value.PreviousFirstChanceExceptionSequenceNumber++; } } private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { if ((Thread.CurrentThread.ThreadState & (ThreadState.AbortRequested | ThreadState.Aborted)) != 0) { return; } using (SuppressFirstChanceExceptionLogMessages()) { try { EventAppDomainException.Publish(null, null, e.ExceptionObject as Exception); } catch (Exception) { //swallow any exceptions. } } } /// /// Looks up the type of the log source /// /// the type /// the classification of the type of messages that this publisher will raise. /// public static LogPublisher CreatePublisher(Type type, MessageClass classification) { return new LogPublisher(s_logger, s_logger.CreateType(type), classification); } /// /// Creates a /// /// public static LogSubscriber CreateSubscriber(VerboseLevel level = VerboseLevel.None) { var subscriber = new LogSubscriber(s_logger.CreateSubscriber()); subscriber.SubscribeToAll(level); return subscriber; } /// /// Logs that a first chance exception was intentionally not handled for the provided reason. /// In the LogFileViewer it will filter messages differently if it was indicated that they were swallowed. /// /// the exception that was swallowed /// message to include, such as a reason why it was swallowed. /// additional details. /// additional flags that can be set with this swallowed exception. public static void SwallowException(Exception ex, string message = null, string details = null, MessageFlags additionalFlags = MessageFlags.None) { EventSwallowedException.Publish(additionalFlags, message, details, ex); //Increment this value to ensure that nothing else matches with the exception since it has been removed. ThreadLocalThreadStack.Value.PreviousFirstChanceExceptionSequenceNumber++; } /// /// Searches the current stack frame for all related messages that will be published with this message. /// /// public static LogStackMessages GetStackMessages() { return ThreadLocalThreadStack.Value.GetStackMessages(); } /// /// Temporarily appends data to the thread's stack so the data can be propagated to any messages generated on this thread. /// Be sure to call Dispose on the returned object to remove this from the stack. /// /// /// public static IDisposable AppendStackMessages(LogStackMessages messages) { return ThreadLocalThreadStack.Value.AppendStackMessages(messages); } /// /// Sets a flag that will prevent log messages from being raised on this thread. /// Remember to dispose of the callback to remove this suppression. /// /// public static IDisposable SuppressLogMessages() { return ThreadLocalThreadStack.Value.SuppressLogMessages(SuppressionMode.AllMessages); } /// /// Sets a flag that will prevent First Chance Exception log messages from being raised on this thread. /// Remember to dispose of the callback to remove this suppression. /// /// public static IDisposable SuppressFirstChanceExceptionLogMessages() { return ThreadLocalThreadStack.Value.SuppressLogMessages(SuppressionMode.FirstChanceExceptionOnly); } /// /// Sets a flag that will allow log messages to be raised again. /// Remember to dispose of the callback to remove this override. /// /// public static IDisposable OverrideSuppressLogMessages() { return ThreadLocalThreadStack.Value.SuppressLogMessages(SuppressionMode.None); } /// /// Temporarily appends data to the thread's stack so the data can be propagated to any messages generated on this thread. /// Be sure to call Dispose on the returned object to remove this from the stack. /// /// public static IDisposable AppendStackMessages(string key, string value) { return AppendStackMessages(new LogStackMessages(key, value)); } private static void GrowStackDisposal(int desiredSize) { //Since these depths are relatively small, growing them both together has minor consequence. lock (SyncRoot) { while (s_stackDisposalStackMessages == null || s_stackDisposalStackMessages.Length < desiredSize) { //Note: both are grown together and completely reinitialized to improve // locality of reference. int lastSize = s_stackDisposalStackMessages?.Length ?? 2; StackDisposal[] stackMessages = new StackDisposal[lastSize * 2]; for (int x = 0; x < stackMessages.Length; x++) { stackMessages[x] = new StackDisposal(x, DisposeStackMessage); } StackDisposal[] suppressionFlags = new StackDisposal[lastSize * 2]; for (int x = 0; x < suppressionFlags.Length; x++) { suppressionFlags[x] = new StackDisposal(x, DisposeSuppressionFlags); } s_stackDisposalStackMessages = stackMessages; s_stackDisposalSuppressionFlags = suppressionFlags; } } } private static void DisposeStackMessage(int depth) { ThreadLocalThreadStack.Value.RemoveStackMessage(depth); } private static void DisposeSuppressionFlags(int depth) { ThreadLocalThreadStack.Value.RemoveSuppression(depth); } /// /// A class that will undo a temporary change in the stack variables. Note, this class /// will be reused. Therefore setting some kind of disposed flag will cause make this /// class unusable. The side effect of multiple calls to Dispose is tolerable. /// private class StackDisposal : IDisposable { private readonly int m_depth; private readonly Action m_callback; internal StackDisposal(int depth, Action callback) { m_depth = depth; m_callback = callback; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { m_callback(m_depth); } } } }