using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using Wayne.Lib.IO; namespace Wayne.Lib.Log { /// /// Logger is a class used to create log objects. /// public class Logger { #region Fields /// /// Default date and time format to use in log files. /// public const string DefaultDateTimeFormat = "HH':'mm':'ss'.'fff'[#]'"; private const int DefaultDebugMarkerBroadcastPort = 11001; private readonly List OutstandingLogObjects = new List(); private readonly object OutstandingLogObjectsLock = new object(); // Locking object to safely access the OutstandingLogObjects. private readonly Dictionary EventSubscriberDict = new Dictionary(); private readonly object EventSubscriberDictSyncObj = new object(); private readonly Dictionary ExternalLogWriterNameDict = new Dictionary(); private readonly Dictionary ExternalLogWriterWrapperDict = new Dictionary(); private readonly object ExternalLogWriterDictSyncObj = new object(); private readonly byte[] DebugMarkerBroadcastReceiverBuffer = new byte[100]; private readonly List DebugConfigFileNameList = new List(); private readonly List EventConfigFileNameList = new List(); private readonly List DebugConfigFilesList = new List(); private readonly List EventConfigFilesList = new List(); private readonly List DebugConfigBuilderList = new List(); private readonly List EventConfigBuilderList = new List(); private bool active = false; private LoggerThread loggerThread; private LoggerThread eventLoggerThread; private LogConfig debugConfig; private LogConfig eventConfig; private DotNetLog dotNetLog; private GlobalLogEntryCounter globalLogEntryCounter; private IPEndPoint debugMarkerBroadcastAddress; private System.Net.Sockets.Socket debugMarkerBroadcastReceiverSocket; #endregion #region Construction Logger() { ThreadPriority=ThreadPriority.Lowest; Reset(); } #endregion #region Internal Properties /// /// Tells whether someone has called the Close() method. /// public bool IsClosed { get { return !active; } } /// /// Internal access to the DebugConfig. /// internal LogConfig DebugConfig { get { return debugConfig; } } /// /// Internal access to the GlobalLogEntryCounter. /// internal GlobalLogEntryCounter GlobalLogEntryCounter { get { return globalLogEntryCounter; } } #endregion #region Internal Methods /// /// This method is used to fire an event instead of throwing an exception if something /// "crashes" in a thread (in order to keep the thread alive but still "report" exceptions). /// /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] internal void FireOnThreadException(LogException exception) { try { if (OnThreadException != null) OnThreadException(null, new EventArgs(exception)); } catch (Exception) { } } /// /// Internal method that is used by the EventLogSubscriptionLogWriter to publish an /// event entry to a specific subscriber. /// /// /// internal void PublishEventLog(string eventSubscriberId, EventLogEntry eventLogEntry) { //eventLogStorage.Add(eventSubscriberId, eventLogEntry, storageType); IEventSubscriber eventSubscriber; lock (EventSubscriberDictSyncObj) { EventSubscriberDict.TryGetValue(eventSubscriberId, out eventSubscriber); } if (eventSubscriber != null) eventSubscriber.HandleEvent(eventLogEntry); } #endregion #region Internal Methods: Persistent log objects internal void RegisterPersistentLogObject(DebugLogger debugLogger) { lock (OutstandingLogObjectsLock) { if (!OutstandingLogObjects.Contains(debugLogger)) OutstandingLogObjects.Add(debugLogger); } } internal void UnregisterPersistentLogObject(DebugLogger debugLogger) { lock (OutstandingLogObjectsLock) { if (OutstandingLogObjects.Contains(debugLogger)) OutstandingLogObjects.Remove(debugLogger); } } #endregion #region Public Events /// /// An event that is fired when the logging thread is catching an exception. /// public event EventHandler> OnThreadException; #endregion #region Public Properties /// /// Is any debug logging configured? /// public bool DebugLoggingConfigured { get { return debugConfig.IsConfigured; } } /// /// Is any event logging configured? /// public bool EventLoggingConfigured { get { return eventConfig.IsConfigured; } } /// /// Should the logger be synchronized or not? /// Default is false (the log writing is performed in another thread). /// public bool Synchronized { get; set; } /// /// Priority for the Logger threads. Defaults to Lowest. /// public ThreadPriority ThreadPriority { get; set; } #endregion #region Public Methods: ConfigFile handling /// /// Clear all loaded log configuration . /// public void ClearConfigFiles() { if (!active) return; DebugConfigFileNameList.Clear(); EventConfigFileNameList.Clear(); DebugConfigFilesList.Clear(); EventConfigFilesList.Clear(); DebugConfigBuilderList.Clear(); EventConfigBuilderList.Clear(); RefreshConfig(); } /// /// Reloads the configuration from the specified configuration file. /// /// Log configuration for the debug logging. /// Log configuration for the event logging. public void SetConfigFile(string debugConfigFileName, string eventConfigFileName) { if (!active) return; ClearConfigFiles(); AddDebugConfigFile(debugConfigFileName); AddEventConfigFile(eventConfigFileName); } /// /// Reloads the configuration from the specified configuration file for the debug logging. To activate the event logging /// SetConfigFile(string,string) should be called. /// /// Log configuration for the debug logging. public void SetConfigFile(string debugConfigFileName) { if (!active) return; ClearConfigFiles(); AddDebugConfigFile(debugConfigFileName); } /// /// Reloads the configuration, adding the given debug log config file. /// /// Log configuration for the debug logging. public void AddDebugConfigFile(string debugConfigFileName) { if (!active) return; DebugConfigFileNameList.Add(debugConfigFileName); RefreshConfig(); } /// /// Reloads the configuration, adding the given debug log config file. /// /// Log configurations for the debug logging. public void AddDebugConfigFile(params LogConfigBuilder[] logConfigBuilders) { if (!active) return; DebugConfigBuilderList.AddRange(logConfigBuilders); RefreshConfig(); } /// /// Reloads the configuration, adding the given debug log config file. /// /// Log configuration for the debug logging. public void AddDebugConfigFile(Stream debugConfigFileStream) { AddDebugConfigFile(debugConfigFileStream, System.Text.Encoding.UTF8); } /// /// Reloads the configuration, adding the given debug log config file. /// /// Log configuration for the debug logging. /// The encoding of the stream. public void AddDebugConfigFile(Stream debugConfigFileStream, System.Text.Encoding encoding) { if (!active) return; DebugConfigFilesList.Add(GetFileLines(debugConfigFileStream, encoding)); RefreshConfig(); } /// /// Reloads the configuration, adding the given event log config file. /// /// Log configuration for the event logging. public void AddEventConfigFile(string eventConfigFileName) { if (!active) return; EventConfigFileNameList.Add(eventConfigFileName); RefreshConfig(); } /// /// Reloads the configuration, adding the given event log config file. /// /// Log configurations for the event logging. public void AddEventConfigFile(params LogConfigBuilder[] logConfigBuilders) { if (!active) return; EventConfigBuilderList.AddRange(logConfigBuilders); RefreshConfig(); } /// /// Reloads the configuration, adding the given event log config file. /// /// Log configuration for the event logging. public void AddEventConfigFile(Stream eventConfigFileStream) { AddDebugConfigFile(eventConfigFileStream, System.Text.Encoding.UTF8); } /// /// Reloads the configuration, adding the given event log config file. /// /// Log configuration for the event logging. /// The encoding of the stream. public void AddEventConfigFile(Stream eventConfigFileStream, System.Text.Encoding encoding) { if (!active) return; EventConfigFilesList.Add(GetFileLines(eventConfigFileStream, encoding)); RefreshConfig(); } private string[] GetFileLines(Stream fileStream, System.Text.Encoding encoding) { List lines = new List(); using (StreamReader reader = new StreamReader(fileStream, encoding)) { while (!reader.EndOfStream) lines.Add(reader.ReadLine()); } return lines.ToArray(); } #endregion #region Public Methods: Refresh /// /// Re-loads the configuration for the logging. /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] [SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")] public void RefreshConfig() { if (!active) return; InitDebugMarkerBroadcastListener(); // Refresh the DebugConfig. List debugConfigFiles = new List(DebugConfigFilesList); foreach (string fileName in DebugConfigFileNameList) debugConfigFiles.Add(FileSupport.LoadToStringArray(fileName)); if (DebugConfigBuilderList.Count > 0) debugConfigFiles.Add(LogConfigBuilder.GetLogConfigFileLines(DebugConfigBuilderList.ToArray())); DebugConfig.Refresh(debugConfigFiles, new LogConfigOutput[0], new LogConfigOutput[0]); // Refresh the EventConfig. List eventConfigFiles = new List(EventConfigFilesList); foreach (string fileName in EventConfigFileNameList) eventConfigFiles.Add(FileSupport.LoadToStringArray(fileName)); if (EventConfigBuilderList.Count > 0) eventConfigFiles.Add(LogConfigBuilder.GetLogConfigFileLines(EventConfigBuilderList.ToArray())); eventConfig.Refresh(eventConfigFiles, new LogConfigOutput[0], new LogConfigOutput[0]); // Start the thread if it's not yet started (first time it's null). if ((loggerThread == null) && (debugConfigFiles.Count > 0)) loggerThread = new LoggerThread(LogType.Debug, debugConfig); if ((eventLoggerThread == null) && (eventConfigFiles.Count > 0)) eventLoggerThread = new LoggerThread(LogType.Event, eventConfig); // To prevent locking the list while iterating, take a copy and iterate through the copy. // The danger of doing this is that an object could be removed after the copy and before its // Invalidate-method is called. But nothing really bad can happen due to this... List outstandingLogObjectsCopy; lock (OutstandingLogObjectsLock) { outstandingLogObjectsCopy = new List(OutstandingLogObjects); } foreach (DebugLogger debugLogger in outstandingLogObjectsCopy) { try { debugLogger.Invalidate(); } catch (Exception) { } } // Check if the dotNetLog should be active or not. dotNetLog.CheckActive(); } #endregion #region Public Methods: Reset /// /// Resets the logger completely. /// public void Reset() { Close(); active = true; debugConfig = new LogConfig(); eventConfig = new LogConfig(); dotNetLog = new DotNetLog(); globalLogEntryCounter = new GlobalLogEntryCounter(); } #endregion #region Public Methods: Close /// /// Closes the logger. This should be done as the last things before the application terminates. /// public void Close() { if (active) { // Shut down the config. if (debugConfig != null) { debugConfig.Dispose(); debugConfig = null; } if (eventConfig != null) { eventConfig.Dispose(); eventConfig = null; } if (dotNetLog != null) { // Shut down the dotNetLog. dotNetLog.Dispose(); dotNetLog = null; } ExternalLogWriterNameDict.Clear(); ExternalLogWriterWrapperDict.Clear(); // Now we're closed. active = false; // Shut down the loggerThread. if (loggerThread != null) { loggerThread.Dispose(); loggerThread = null; } if (eventLoggerThread != null) { eventLoggerThread.Dispose(); eventLoggerThread = null; } if (debugMarkerBroadcastReceiverSocket != null) { try { if (debugMarkerBroadcastReceiverSocket.Connected) { debugMarkerBroadcastReceiverSocket.Shutdown(SocketShutdown.Both); } } catch { } try { debugMarkerBroadcastReceiverSocket.Close(); } catch { } debugMarkerBroadcastReceiverSocket = null; } if (globalLogEntryCounter != null) { globalLogEntryCounter.Dispose(); globalLogEntryCounter = null; } debugMarkerBroadcastAddress = null; DebugConfigFileNameList.Clear(); DebugConfigFilesList.Clear(); EventConfigFileNameList.Clear(); EventConfigFilesList.Clear(); DebugConfigBuilderList.Clear(); EventConfigBuilderList.Clear(); OutstandingLogObjects.Clear(); EventSubscriberDict.Clear(); Synchronized = false; } } #endregion #region Public Methods: LogEntry /// /// Logs the given LogEntry. /// /// The LogEntry to log. public void AddEntry(LogEntry logEntry) { if (!active) return; if (loggerThread != null) loggerThread.AddEntry(logEntry); if ((eventLoggerThread != null) && (logEntry is EventLogEntry)) eventLoggerThread.AddEntry(logEntry); // eventThread has higher priority? } /// /// Logs the given ExceptionLogEntry. /// /// The LogEntry to log. public void AddExceptionLogEntry(ExceptionLogEntry logEntry) { AddEntry(logEntry); } /// /// Logs the given ErrorLogEntry. /// /// The LogEntry to log. public void AddErrorLogEntry(ErrorLogEntry logEntry) { AddEntry(logEntry); } /// /// Logs the given EventLogEntry. /// /// The LogEntry to log. public void AddEventLogEntry(EventLogEntry logEntry) { AddEntry(logEntry); } #endregion #region Public Methods: Unregister entity /// /// Removes the specified entity from the internal filter buffers. /// /// public void UnregisterEntity(IIdentifiableEntity entity) { if (loggerThread != null) { loggerThread.AddEntry(new UnregisterEntity(entity)); } else { if (debugConfig != null) { debugConfig.UnregisterEntity(entity); } } if (eventLoggerThread != null) { eventLoggerThread.AddEntry(new UnregisterEntity(entity)); } else { if (eventConfig != null) { eventConfig.UnregisterEntity(entity); } } } #endregion #region Public Methods for Event Subscribers /// /// Register an IEventSubscriber, so it can start receiving events. The event subscriber will be sent the pending events that /// has been stored since the subscriber was registered the last time. /// /// public void RegisterEventSubscriber(IEventSubscriber eventSubscriber) { if (eventSubscriber == null) throw new ArgumentNullException("eventSubscriber"); lock (EventSubscriberDictSyncObj) { EventSubscriberDict[eventSubscriber.SubscriberId] = eventSubscriber; } } /// /// Unregister a registered IEventSubscriber. /// /// public void UnregisterEventSubscriber(IEventSubscriber eventSubscriber) { if (eventSubscriber == null) throw new ArgumentNullException("eventSubscriber"); lock (EventSubscriberDictSyncObj) { EventSubscriberDict.Remove(eventSubscriber.SubscriberId); } } #endregion #region ExternalLogging private string GetExternalLogWriterKey(ExternalLogWriter externalLogWriter) { return string.Format("{0}#{1}", externalLogWriter.ExternalLogType, externalLogWriter.ExternalLogName); } private string GetExternalLogWriterKey(ExternalLogWriterWrapper externalLogWriterWrapper) { return string.Format("{0}#{1}", externalLogWriterWrapper.ExternalLogType, externalLogWriterWrapper.ExternalLogName); } /// /// Register an ExternalLogWriter. /// /// The external log writer to register. public void RegisterExternalLogger(ExternalLogWriter externalLogWriter) { if (ExternalLogWriterDictSyncObj != null) lock (ExternalLogWriterDictSyncObj) ExternalLogWriterNameDict[GetExternalLogWriterKey(externalLogWriter)] = externalLogWriter; } /// /// Unregister an ExternalLogWriter. /// /// The external log writer to unregister. public void UnregisterExternalLogger(ExternalLogWriter externalLogWriter) { if (ExternalLogWriterDictSyncObj != null) lock (ExternalLogWriterDictSyncObj) { ExternalLogWriterNameDict.Remove(GetExternalLogWriterKey(externalLogWriter)); bool found; do { found = false; foreach (KeyValuePair pair in ExternalLogWriterWrapperDict) { if (pair.Value == externalLogWriter) { ExternalLogWriterWrapperDict.Remove(pair.Key); found = true; break; } } } while (found); } } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] internal void PerformExternalLogEntry(ExternalLogWriterWrapper externalLogWriterWrapper, LogEntry logEntry) { if (ExternalLogWriterDictSyncObj != null) { lock (ExternalLogWriterDictSyncObj) { ExternalLogWriter externalLogWriter; if (!ExternalLogWriterWrapperDict.TryGetValue(externalLogWriterWrapper, out externalLogWriter)) { if (ExternalLogWriterNameDict.TryGetValue(GetExternalLogWriterKey(externalLogWriterWrapper), out externalLogWriter)) { externalLogWriter.InitParametersInternal(externalLogWriterWrapper.Parameters); ExternalLogWriterWrapperDict[externalLogWriterWrapper] = externalLogWriter; } } if (externalLogWriter != null) { try { if (externalLogWriter.Active) { string text = LogTextWriting.GetLogEntryText(externalLogWriterWrapper, logEntry, externalLogWriterWrapper.WritingParameters); externalLogWriter.LogInternal(logEntry, text); } } catch { } } } } } /// /// Get the LogTextWritingParameters for the requested ExternalLogWriter. /// /// The ExternalLogWriter to get the parameters of. [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] internal LogTextWritingParameters GetExternalLoggerWritingParameters(ExternalLogWriter externalLogWriter) { if (ExternalLogWriterDictSyncObj != null) { lock (ExternalLogWriterDictSyncObj) { foreach (KeyValuePair pair in ExternalLogWriterWrapperDict) { if (pair.Value == externalLogWriter) { return pair.Key.WritingParameters; } } } } return null; } #endregion #region Debug Marker Broadcasting /// /// Broadcasts a debug marker to be inserted into all active logfiles. /// /// The text to insert into all the log files. public void InjectDebugMarker(string debugMarker) { try { System.Net.Sockets.Socket transmitterSocket = null; try { try { transmitterSocket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.IP); transmitterSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); var lingerOption = new LingerOption(true, 0); transmitterSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lingerOption); transmitterSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, 1); transmitterSocket.Bind(new IPEndPoint(IPAddress.Any, 0)); // Bind the socket to the "any" end point. byte[] data = System.Text.Encoding.UTF8.GetBytes(debugMarker); int offset = 0; int size = data.Length; if (size > DebugMarkerBroadcastReceiverBuffer.Length) size = DebugMarkerBroadcastReceiverBuffer.Length; int currentSentSize; do { currentSentSize = transmitterSocket.SendTo(data, offset, size, SocketFlags.None, GetDebugMarkerBroadcastIPEndPoint()); offset += currentSentSize; size -= currentSentSize; } while ((size > 0) && (currentSentSize > 0)); } catch (Exception) { } } finally { if (transmitterSocket != null) { try { transmitterSocket.Shutdown(SocketShutdown.Both); } finally { transmitterSocket.Close(); } } } } catch (Exception) { } } private void InitDebugMarkerBroadcastListener() { if (debugMarkerBroadcastReceiverSocket == null) { try { debugMarkerBroadcastReceiverSocket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.IP); debugMarkerBroadcastReceiverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); debugMarkerBroadcastReceiverSocket.Bind(new IPEndPoint(IPAddress.Any, GetDebugMarkerBroadcastIPEndPoint().Port)); // Bind the socket to the "any" end point. debugMarkerBroadcastReceiverSocket.BeginReceive(DebugMarkerBroadcastReceiverBuffer, 0, DebugMarkerBroadcastReceiverBuffer.Length, 0, ReceiveCallback, debugMarkerBroadcastReceiverSocket); // Start to listen. } catch { try { debugMarkerBroadcastReceiverSocket.Close(); } catch (Exception) { } debugMarkerBroadcastReceiverSocket = null; } } } private void ReceiveCallback(IAsyncResult ar) { if (debugMarkerBroadcastReceiverSocket != null) { try { try { System.Net.Sockets.Socket socket = (System.Net.Sockets.Socket)ar.AsyncState; int bytesReceived = 0; try { try { bytesReceived = socket.EndReceive(ar); } catch { } } finally { if (bytesReceived > 0) { string debugMarker = System.Text.Encoding.UTF8.GetString(DebugMarkerBroadcastReceiverBuffer, 0, bytesReceived); AddEntry(new DebugMarkerLogEntry(debugMarker)); } } } finally { if (active) debugMarkerBroadcastReceiverSocket.BeginReceive(DebugMarkerBroadcastReceiverBuffer, 0, DebugMarkerBroadcastReceiverBuffer.Length, 0, ReceiveCallback, debugMarkerBroadcastReceiverSocket); } } catch { } } } [System.Diagnostics.DebuggerStepThrough()] private IPEndPoint GetDebugMarkerBroadcastIPEndPoint() { try { if (debugMarkerBroadcastAddress == null) { //using (Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"Software\Dresser Wayne\ISM Nucleus\TCP IP\UDP Broadcast", false)) { IPAddress broadcastAddress; int broadcastPort; //if (key != null) //{ // broadcastAddress = IPAddress.Parse((string)key.GetValue("BroadcastAddress", "255.255.255.255")); // broadcastPort = (int)key.GetValue("LogMarkerInjectorPort", DefaultDebugMarkerBroadcastPort); //} //else { broadcastPort = DefaultDebugMarkerBroadcastPort; broadcastAddress = IPAddress.Broadcast; } if (broadcastAddress != null) debugMarkerBroadcastAddress = new IPEndPoint(broadcastAddress, broadcastPort); } } } catch { if (debugMarkerBroadcastAddress == null) debugMarkerBroadcastAddress = new IPEndPoint(IPAddress.Broadcast, DefaultDebugMarkerBroadcastPort); } return debugMarkerBroadcastAddress; } #endregion #region DateTimeToString internal string DateTimeToString(DateTime dateTime, string dateTimeFormat) { if (GlobalLogEntryCounter != null) return DateTimeToString(dateTime, dateTimeFormat, GlobalLogEntryCounter.GetNextValue()); return DateTimeToString(dateTime, dateTimeFormat, 0); } internal string DateTimeToString(DateTime dateTime, string dateTimeFormat, UInt64 logEntryIndex) { return FormatDateTime(dateTime) + "[" + logEntryIndex.ToString("00000") + "]"; } private string FormatDateTime(DateTime dt) { char[] chars = new char[12]; Write2Chars(chars, 0, dt.Hour); chars[2] = ':'; Write2Chars(chars, 3, dt.Minute); chars[5] = ':'; Write2Chars(chars, 6, dt.Second); chars[8] = '.'; Write2Chars(chars, 9, dt.Millisecond / 10); chars[11] = Digit(dt.Millisecond % 10); return new string(chars); } private void Write2Chars(char[] chars, int offset, int value) { chars[offset] = Digit(value / 10); chars[offset + 1] = Digit(value % 10); } private char Digit(int value) { return (char)(value + '0'); } #endregion } public class UnregisterEntity : LogEntry { public UnregisterEntity(IIdentifiableEntity entity) : base(entity, null) { } } }