/// <summary> /// Writes a system-level error message with exception. /// </summary> /// <remarks> /// This logging method is used for the most important high-level events the user should be aware of. /// These events are logged into the Windows Event log instead of the trace debug log files, and would be /// used for logging exceptions, backup completed/sync status, configuration issues, etc. /// </remarks> /// <param name="message"></param> /// <param name="exception"></param> /// <param name="stackContext"></param> /// <param name="eventID"></param> /// <param name="writeToTraceLog"></param> /// <param name="engineID"></param> public void WriteSystemEvent(string message, Exception exception, string stackContext, int eventID, bool writeToTraceLog, int engineID = 0) { if (EventLogInitialized == false) { throw new InvalidOperationException("Log has not been initialized."); } if (string.IsNullOrWhiteSpace(message)) { throw new ArgumentException("Argument cannot be null or empty/whitespace: " + nameof(message)); } if (exception == null) { throw new ArgumentNullException(nameof(exception)); } var loggableMessage = GenerateExceptionLoggingMessage(message, exception, stackContext); EventLog.WriteEntry(LogSource, loggableMessage, EventLogEntryType.Error, eventID); if (writeToTraceLog && TraceInitialized) { // also append this message to the tracelog. TraceMessageQueue.Enqueue(loggableMessage); } }
/// <summary> /// Writes an error message to the trace log file on disk. /// </summary> /// <remarks> /// This logging method is the preferred method for trace-level debug messaging. /// An example would be things like individual file transfer messages, state changes, etc. /// </remarks> /// <param name="message"></param> /// <param name="engineID"></param> public void WriteTraceError(string message, int engineID = 0) { if (TraceInitialized == false) { throw new InvalidOperationException("Trace has not been initialized."); } if (string.IsNullOrWhiteSpace(message)) { throw new ArgumentException("Argument cannot be null or empty/whitespace: " + nameof(message)); } TraceMessageQueue.Enqueue(PrependMessageWithDateAndSeverity(message, EventLogEntryType.Error, engineID)); }
/// <summary> /// Writes an error message (and exception) to the trace log file on disk. /// </summary> /// <remarks> /// This logging method is the preferred method for trace-level debug messaging. /// An example would be things like individual file transfer messages, state changes, etc. /// </remarks> /// <param name="message"></param> /// <param name="exception"></param> /// <param name="stackContext"></param> /// <param name="engineID"></param> public void WriteTraceError(string message, Exception exception, string stackContext, int engineID = 0) { if (TraceInitialized == false) { throw new InvalidOperationException("Trace has not been initialized."); } if (string.IsNullOrWhiteSpace(message)) { throw new ArgumentException("Argument cannot be null or empty/whitespace: " + nameof(message)); } if (exception == null) { throw new ArgumentNullException(nameof(exception)); } TraceMessageQueue.Enqueue(GenerateExceptionLoggingMessage(message, exception, stackContext)); }
/// <summary> /// Writes a system-level event message. /// </summary> /// <remarks> /// This logging method is used for the most important high-level events the user should be aware of. /// These events are logged into the Windows Event log instead of the trace debug log files, and would be /// used for logging exceptions, backup completed/sync status, configuration issues, etc. /// </remarks> /// <param name="message"></param> /// <param name="severity"></param> /// <param name="eventID"></param> /// <param name="writeToTraceLog"></param> /// <param name="engineID"></param> public void WriteSystemEvent(string message, EventLogEntryType severity, int eventID, bool writeToTraceLog, int engineID = 0) { if (EventLogInitialized == false) { throw new InvalidOperationException("Log has not been initialized."); } if (string.IsNullOrWhiteSpace(message)) { throw new ArgumentException("Argument cannot be null or empty/whitespace: " + nameof(message)); } var loggableMessage = PrependMessageWithDateAndSeverity(message, severity, engineID); EventLog.WriteEntry(LogSource, loggableMessage, severity, eventID); if (writeToTraceLog && TraceInitialized) { // also append this message to the tracelog. TraceMessageQueue.Enqueue(loggableMessage); } }
/// <summary> /// A long-running loop/thread to write trace messages to disk. /// </summary> /// <remarks> /// A dedicated message writer thread with retry solves two problems: /// 1) We can queue messages for writing- which allows the logging consumers to return immediately and not wait for logging to flush to disk. /// 2) Writing to a file on disk may encounter write/lock issues. /// They usually crop up from things like antivirus scans. If a single log message fails to write, /// then this shouldn't crash the application. This function is a loop to write with retries and delays. /// </remarks> public async Task TraceMessageWriterAsync() { while (true) { string messageToWrite = null; if (TraceMessageQueue.Count > 0) { TraceMessageQueue.TryDequeue(out messageToWrite); } if (messageToWrite == null) { await WaitAsync(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } else { bool successfulWrite = false; Exception lastError = null; int attempts = 0; while (true) { attempts++; try { File.AppendAllText(GetCurrentTraceLogFilePath(), messageToWrite + Environment.NewLine); successfulWrite = true; break; } catch (Exception ex) { if (attempts >= MaxWriteAttempts) { lastError = ex; break; } else { if (CancelSource.IsCancellationRequested) { break; } else { await WaitAsync(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } } } } if (successfulWrite == false) { WriteSystemEvent("Failed to write a message to the tracelog.", lastError, null, EventIDs.FailedToWriteToTraceLog, false); } } if (CancelSource.IsCancellationRequested) { break; } } }