//******************************************************************************************************
// 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);
}
}
}
}