using EonaCat.LogStack.Core; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace EonaCat.LogStack.Flows { // This file is part of the EonaCat project(s) which is released under the Apache License. // See the LICENSE file or go to https://EonaCat.com/License for full license details. /// /// Writes log events to the Windows Event Log. /// /// Requires the source to be registered before first use. /// Call once during application setup /// (requires elevated privileges the first time). /// /// .NET 4.8.1 compatible. Silently no-ops on non-Windows platforms. /// public sealed class WindowsEventLogFlow : FlowBase { private readonly string _sourceName; private readonly string _logName; private readonly int _maxMessageLength; private System.Diagnostics.EventLog _eventLog; private readonly object _initLock = new object(); private volatile bool _initialized; public WindowsEventLogFlow( string sourceName = "EonaCatLogStack", string logName = "Application", int maxMessageLength = 30000, LogLevel minimumLevel = LogLevel.Warning) : base("WindowsEventLog:" + sourceName, minimumLevel) { if (sourceName == null) { throw new ArgumentNullException("sourceName"); } if (logName == null) { throw new ArgumentNullException("logName"); } _sourceName = sourceName; _logName = logName; _maxMessageLength = maxMessageLength; } /// /// Registers the event source with the OS. Must be called with admin rights /// the first time on each machine. Safe to call repeatedly. /// public static void EnsureSourceExists(string sourceName = "EonaCatLogStack", string logName = "Application") { if (!IsWindows()) { return; } try { if (!System.Diagnostics.EventLog.SourceExists(sourceName)) { System.Diagnostics.EventLog.CreateEventSource(sourceName, logName); } } catch (Exception ex) { Console.Error.WriteLine("[WindowsEventLogFlow] Cannot create source: " + ex.Message); } } public override Task BlastAsync( LogEvent logEvent, CancellationToken cancellationToken = default(CancellationToken)) { if (!IsEnabled || !IsLogLevelEnabled(logEvent)) { return Task.FromResult(WriteResult.LevelFiltered); } if (!IsWindows()) { return Task.FromResult(WriteResult.Success); } EnsureInitialized(); if (_eventLog == null) { return Task.FromResult(WriteResult.Dropped); } try { string msg = BuildMessage(logEvent); if (msg.Length > _maxMessageLength) { msg = msg.Substring(0, _maxMessageLength) + "... [truncated]"; } _eventLog.WriteEntry(msg, ToEventType(logEvent.Level), ToEventId(logEvent.Level)); Interlocked.Increment(ref BlastedCount); return Task.FromResult(WriteResult.Success); } catch (Exception ex) { Console.Error.WriteLine("[WindowsEventLogFlow] Write error: " + ex.Message); Interlocked.Increment(ref DroppedCount); return Task.FromResult(WriteResult.Dropped); } } public override Task BlastBatchAsync( ReadOnlyMemory logEvents, CancellationToken cancellationToken = default(CancellationToken)) { if (!IsEnabled) { return Task.FromResult(WriteResult.FlowDisabled); } foreach (LogEvent e in logEvents.ToArray()) { if (IsLogLevelEnabled(e)) { BlastAsync(e, cancellationToken); } } return Task.FromResult(WriteResult.Success); } public override Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) => Task.FromResult(0); public override async ValueTask DisposeAsync() { IsEnabled = false; if (_eventLog != null) { try { _eventLog.Dispose(); } catch { } } await base.DisposeAsync().ConfigureAwait(false); } // ----------------------------------------------------------------- helpers private void EnsureInitialized() { if (_initialized) { return; } lock (_initLock) { if (_initialized) { return; } try { if (System.Diagnostics.EventLog.SourceExists(_sourceName)) { _eventLog = new System.Diagnostics.EventLog(_logName) { Source = _sourceName }; } else { Console.Error.WriteLine( "[WindowsEventLogFlow] Source '" + _sourceName + "' not registered. Call EnsureSourceExists() with admin rights."); } } catch (Exception ex) { Console.Error.WriteLine("[WindowsEventLogFlow] Init error: " + ex.Message); } _initialized = true; } } private static string BuildMessage(LogEvent log) { var sb = new System.Text.StringBuilder(512); sb.Append("Level: ").AppendLine(LevelString(log.Level)); sb.Append("Category: ").AppendLine(log.Category ?? string.Empty); sb.Append("Time: ").AppendLine(LogEvent.GetDateTime(log.Timestamp).ToString("O")); sb.Append("Message: ").AppendLine(log.Message.Length > 0 ? log.Message.ToString() : string.Empty); if (log.Exception != null) { sb.Append("Exception: ").AppendLine(log.Exception.ToString()); } if (log.Properties.Count > 0) { sb.AppendLine("Properties:"); foreach (var kv in log.Properties.ToArray()) { sb.Append(" ").Append(kv.Key).Append(" = ") .AppendLine(kv.Value != null ? kv.Value.ToString() : "null"); } } return sb.ToString(); } private static System.Diagnostics.EventLogEntryType ToEventType(LogLevel level) { switch (level) { case LogLevel.Warning: return System.Diagnostics.EventLogEntryType.Warning; case LogLevel.Error: case LogLevel.Critical: return System.Diagnostics.EventLogEntryType.Error; default: return System.Diagnostics.EventLogEntryType.Information; } } private static int ToEventId(LogLevel level) { // Stable event IDs per level for easy filtering in Event Viewer switch (level) { case LogLevel.Trace: return 1000; case LogLevel.Debug: return 1001; case LogLevel.Information: return 1002; case LogLevel.Warning: return 1003; case LogLevel.Error: return 1004; case LogLevel.Critical: return 1005; default: return 1999; } } private static string LevelString(LogLevel level) { switch (level) { case LogLevel.Trace: return "TRACE"; case LogLevel.Debug: return "DEBUG"; case LogLevel.Information: return "INFO"; case LogLevel.Warning: return "WARN"; case LogLevel.Error: return "ERROR"; case LogLevel.Critical: return "CRITICAL"; default: return level.ToString().ToUpperInvariant(); } } private static bool IsWindows() { #if NET48 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET481 return true; #else return System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); #endif } } }