Initial version

This commit is contained in:
2026-02-28 07:19:29 +01:00
parent b5f4af6930
commit 3f3356eb4a
183 changed files with 90406 additions and 63 deletions

View File

@@ -0,0 +1,258 @@
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.
/// <summary>
/// Writes log events to the Windows Event Log.
///
/// Requires the source to be registered before first use.
/// Call <see cref="EnsureSourceExists"/> once during application setup
/// (requires elevated privileges the first time).
///
/// .NET 4.8.1 compatible. Silently no-ops on non-Windows platforms.
/// </summary>
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;
}
/// <summary>
/// Registers the event source with the OS. Must be called with admin rights
/// the first time on each machine. Safe to call repeatedly.
/// </summary>
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<WriteResult> 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<WriteResult> BlastBatchAsync(
ReadOnlyMemory<LogEvent> 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
}
}
}