1083 lines
31 KiB
C#
1083 lines
31 KiB
C#
using EonaCat.LogStack.Boosters;
|
||
using EonaCat.LogStack.Core;
|
||
using EonaCat.LogStack.EonaCatLogStackCore;
|
||
using EonaCat.LogStack.EonaCatLogStackCore.Policies;
|
||
using EonaCat.LogStack.Flows;
|
||
using ServiceMonitoring;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data.Common;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net.Http;
|
||
using System.Net.Security;
|
||
using System.Security.Cryptography.X509Certificates;
|
||
|
||
namespace EonaCat.LogStack.Configuration;
|
||
|
||
// 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>
|
||
/// Fluent builder for configuring the logger with flows and boosters
|
||
/// </summary>
|
||
public sealed class LogBuilder
|
||
{
|
||
private readonly string _category;
|
||
private LogLevel _minimumLevel = LogLevel.Trace;
|
||
private TimestampMode _timestampMode = TimestampMode.Utc;
|
||
private readonly List<IFlow> _flows = new();
|
||
private readonly List<IBooster> _boosters = new();
|
||
|
||
public event EventHandler<LogMessage> OnLog;
|
||
|
||
public LogBuilder(string category = "Application")
|
||
{
|
||
_category = category ?? throw new ArgumentNullException(nameof(category));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets the minimum log level
|
||
/// </summary>
|
||
public LogBuilder WithMinimumLevel(LogLevel level)
|
||
{
|
||
_minimumLevel = level;
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets the timestamp mode
|
||
/// </summary>
|
||
public LogBuilder WithTimestampMode(TimestampMode mode)
|
||
{
|
||
_timestampMode = mode;
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds console output
|
||
/// </summary>
|
||
public LogBuilder WriteToConsole(
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
bool useColors = true)
|
||
{
|
||
_flows.Add(new ConsoleFlow(minimumLevel, useColors, _timestampMode));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds diagnostics
|
||
/// </summary>
|
||
public LogBuilder WriteDiagnostics(
|
||
TimeSpan snapshotInterval = default(TimeSpan),
|
||
bool injectIntoEvents = false,
|
||
bool writeSnapshotEvents = true,
|
||
string snapshotCategory = "Diagnostics",
|
||
IFlow forwardTo = null,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
Func<Dictionary<string, object>> customMetrics = null)
|
||
{
|
||
_flows.Add(new DiagnosticsFlow(
|
||
snapshotInterval,
|
||
injectIntoEvents,
|
||
writeSnapshotEvents,
|
||
snapshotCategory,
|
||
forwardTo,
|
||
minimumLevel,
|
||
customMetrics));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds file output
|
||
/// </summary>
|
||
public LogBuilder WriteToFile(
|
||
string directory,
|
||
string filePrefix = "log",
|
||
long maxFileSize = 100 * 1024 * 1024,
|
||
long maxDirectorySize = 2L * 1024 * 1024 * 1024,
|
||
FileRetentionPolicy fileRetentionPolicy = null,
|
||
int flushIntervalInMilliSeconds = 2000,
|
||
bool useCategoryRouting = false,
|
||
int batchSize = 1,
|
||
LogLevel[]? logLevelsForSeparateFiles = null,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
BackpressureStrategy backpressureStrategy = BackpressureStrategy.Wait,
|
||
FileOutputFormat outputFormat = FileOutputFormat.Text,
|
||
CompressionFormat compression = CompressionFormat.GZip,
|
||
string template = "[{ts}] [Host: {host}] [Category: {category}] [Thread: {thread}] [{logtype}] {message}{props}")
|
||
{
|
||
_flows.Add(new FileFlow(
|
||
directory,
|
||
filePrefix,
|
||
maxFileSize,
|
||
maxDirectorySize,
|
||
fileRetentionPolicy,
|
||
flushIntervalInMilliSeconds,
|
||
batchSize,
|
||
minimumLevel,
|
||
useCategoryRouting,
|
||
logLevelsForSeparateFiles,
|
||
_timestampMode,
|
||
backpressureStrategy,
|
||
outputFormat,
|
||
compression,
|
||
template));
|
||
return this;
|
||
}
|
||
|
||
public LogBuilder WriteToEncryptedFile(
|
||
string directory,
|
||
string filePrefix = "log",
|
||
string password = "EonaCat",
|
||
long maxFileSize = 100 * 1024 * 1024,
|
||
FileRetentionPolicy fileRetentionPolicy = null,
|
||
int flushIntervalInMilliSeconds = 2000,
|
||
bool useCategoryRouting = false,
|
||
LogLevel[]? logLevelsForSeparateFiles = null,
|
||
LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new EncryptedFileFlow(
|
||
directory,
|
||
password,
|
||
filePrefix,
|
||
maxFileSize,
|
||
maxDirectorySize: 2L * 1024 * 1024 * 1024,
|
||
retention: fileRetentionPolicy,
|
||
flushIntervalMs: flushIntervalInMilliSeconds,
|
||
batchSize: 1,
|
||
minimumLevel: minimumLevel,
|
||
useCategoryRouting: useCategoryRouting,
|
||
logLevelsForSeparateFiles: logLevelsForSeparateFiles,
|
||
tsMode: _timestampMode));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Write to a rolling buffer
|
||
/// </summary>
|
||
/// <param name="capacity">Maximum number of events to retain.</param>
|
||
/// <param name="minimumLevel">Minimum level to store in the buffer.</param>
|
||
/// <param name="triggerLevel">
|
||
/// When a log event reaches this level or above, the current buffer
|
||
/// contents are immediately forwarded to <paramref name="triggerTarget"/>.
|
||
/// Set to <c>LogLevel.None</c> (or omit) to disable.
|
||
/// </param>
|
||
/// <param name="triggerTarget">
|
||
/// Flow to forward the buffered context to when the trigger fires.
|
||
/// Can be null even when <paramref name="triggerLevel"/> is set.
|
||
/// </param>
|
||
/// <param name="preContextLines">
|
||
/// How many buffered lines to forward before the triggering event.
|
||
/// Defaults to entire buffer (int.MaxValue).
|
||
/// </param>
|
||
/// <returns></returns>
|
||
public LogBuilder WriteToRollingBuffer(
|
||
int capacity = 500,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
LogLevel triggerLevel = LogLevel.Error,
|
||
IFlow triggerTarget = null,
|
||
int preContextLines = int.MaxValue)
|
||
{
|
||
_flows.Add(new RollingBufferFlow(
|
||
capacity,
|
||
minimumLevel,
|
||
triggerLevel,
|
||
triggerTarget,
|
||
preContextLines));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Decrypt a file which is encrypted by EonaCat Logger
|
||
/// </summary>
|
||
/// <param name="encryptedPath">encrypted file source path</param>
|
||
/// <param name="outputPath">destination path for decrypted file</param>
|
||
/// <param name="password">password used by encryption</param>
|
||
/// <returns></returns>
|
||
public static bool DecryptFile(string encryptedPath, string outputPath, string password)
|
||
{
|
||
return EncryptedFileFlow.DecryptToFile(encryptedPath, outputPath, password);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Database output
|
||
/// </summary>
|
||
public LogBuilder WriteToDatabase(
|
||
Func<DbConnection> connectionFactory,
|
||
string tableName = "logs",
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new DatabaseFlow(
|
||
connectionFactory,
|
||
tableName,
|
||
batchSize,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Snmp traps
|
||
/// </summary>
|
||
public LogBuilder WriteToSnmpTrap(string host, int port = 162, string oid = "1.3.6.1.4.1.9999", LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new SnmpTrapFlow(
|
||
host,
|
||
port,
|
||
oid,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Discord
|
||
/// </summary>
|
||
public LogBuilder WriteToDiscord(
|
||
string webHookUrl,
|
||
string botName = "EonaCatBot",
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new DiscordFlow(
|
||
webHookUrl,
|
||
botName,
|
||
batchSize,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds ElasticSearch
|
||
/// </summary>
|
||
public LogBuilder WriteToElasticSearch(
|
||
string elasticSearchUrl,
|
||
string indexName = "EonaCatIndex",
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new ElasticSearchFlow(
|
||
elasticSearchUrl,
|
||
indexName,
|
||
batchSize,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Telegram
|
||
/// </summary>
|
||
public LogBuilder WriteToTelegram(string botToken, string chatId = "EonaCat", int batchSize = 1, LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new TelegramFlow(
|
||
botToken,
|
||
chatId,
|
||
batchSize,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a tamper-evident audit trail.
|
||
///
|
||
/// Each audit entry is hash-chained: every line stores a SHA-256 of the previous
|
||
/// line's hash + the current entry body, so deletion or modification of any past
|
||
/// entry invalidates all subsequent hashes.
|
||
///
|
||
/// Use <see cref="AuditFlow.Verify(string)"/> to verify file integrity at any time.
|
||
/// </summary>
|
||
/// <param name="directory">Directory where the .audit file is written.</param>
|
||
/// <param name="filePrefix">File name prefix (default: "audit").</param>
|
||
/// <param name="auditLevel">
|
||
/// Which severity levels are recorded in the audit trail:
|
||
/// <list type="bullet">
|
||
/// <item><see cref="AuditLevel.All"/> – every log event (default)</item>
|
||
/// <item><see cref="AuditLevel.WarningAndAbove"/> – Warning, Error, Critical</item>
|
||
/// <item><see cref="AuditLevel.ErrorAndAbove"/> – Error and Critical only</item>
|
||
/// <item><see cref="AuditLevel.CriticalOnly"/> – Critical only</item>
|
||
/// </list>
|
||
/// </param>
|
||
/// <param name="minimumLevel">Minimum <see cref="LogLevel"/> for audit capture.</param>
|
||
/// <param name="includeProperties">Whether structured properties are appended to each entry.</param>
|
||
public LogBuilder WriteToAudit(
|
||
string directory,
|
||
string filePrefix = "audit",
|
||
AuditLevel auditLevel = AuditLevel.All,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
bool includeProperties = true)
|
||
{
|
||
_flows.Add(new AuditFlow(
|
||
directory,
|
||
filePrefix,
|
||
auditLevel,
|
||
minimumLevel,
|
||
includeProperties));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Slack
|
||
/// </summary>
|
||
public LogBuilder WriteToSlack(string webhookUrl, int batchSize = 1, LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new SlackFlow(
|
||
webhookUrl,
|
||
batchSize,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Slack
|
||
/// </summary>
|
||
public LogBuilder WriteToMicrosoftTeams(string webhookUrl, int batchSize = 1, LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new MicrosoftTeamsFlow(
|
||
webhookUrl,
|
||
batchSize,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a TCP flow.
|
||
/// </summary>
|
||
public LogBuilder WriteToTcp(
|
||
string host,
|
||
int port,
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
BackpressureStrategy backpressureStrategy = BackpressureStrategy.Wait,
|
||
bool useTls = false,
|
||
RemoteCertificateValidationCallback certValidationCallback = null,
|
||
X509CertificateCollection clientCertificates = null)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(host))
|
||
{
|
||
throw new ArgumentException("Host cannot be null or empty.", nameof(host));
|
||
}
|
||
|
||
if (port <= 0 || port > 65535)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(port), "Port must be between 1 and 65535.");
|
||
}
|
||
|
||
_flows.Add(new TcpFlow(
|
||
host,
|
||
port,
|
||
batchSize,
|
||
minimumLevel,
|
||
backpressureStrategy,
|
||
useTls,
|
||
certValidationCallback,
|
||
clientCertificates));
|
||
|
||
return this;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Adds a Retry flow to retry failed log writes.
|
||
/// </summary>
|
||
public LogBuilder WriteToRetry(
|
||
IFlow primaryFlow,
|
||
int maxRetries = 3,
|
||
TimeSpan? initialDelay = null,
|
||
bool exponentialBackoff = true)
|
||
{
|
||
if (primaryFlow == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(primaryFlow));
|
||
}
|
||
|
||
_flows.Add(new RetryFlow(
|
||
primaryFlow,
|
||
maxRetries,
|
||
initialDelay ?? TimeSpan.FromMilliseconds(200),
|
||
exponentialBackoff));
|
||
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds an EventLog flow for sending logs to a remote destination.
|
||
/// </summary>
|
||
public LogBuilder WriteToEventLogFlow(
|
||
string destination,
|
||
int port = 514,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
int bufferSize = 100,
|
||
TimeSpan? flushInterval = null,
|
||
bool useTls = false,
|
||
RemoteCertificateValidationCallback? certificateValidationCallback = null,
|
||
X509CertificateCollection? clientCertificates = null)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(destination))
|
||
{
|
||
throw new ArgumentException("Destination cannot be null or empty.", nameof(destination));
|
||
}
|
||
|
||
_flows.Add(new EventLogFlow(
|
||
destination,
|
||
port,
|
||
minimumLevel,
|
||
bufferSize,
|
||
flushInterval ?? TimeSpan.FromSeconds(5),
|
||
useTls,
|
||
certificateValidationCallback,
|
||
clientCertificates));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a Failover flow that switches to a secondary flow if the primary fails.
|
||
/// </summary>
|
||
public LogBuilder WriteToFailover(IFlow primaryFlow, IFlow secondaryFlow, TimeSpan? recoveryCheckInterval = null, int failureThreshold = 5)
|
||
{
|
||
if (primaryFlow == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(primaryFlow));
|
||
}
|
||
|
||
if (secondaryFlow == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(secondaryFlow));
|
||
}
|
||
|
||
_flows.Add(new FailoverFlow(primaryFlow, secondaryFlow, recoveryCheckInterval, failureThreshold));
|
||
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a Syslog TCP flow.
|
||
/// </summary>
|
||
public LogBuilder WriteToSyslogTcp(
|
||
string host,
|
||
int port = 514,
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
BackpressureStrategy backpressureStrategy = BackpressureStrategy.Wait,
|
||
bool useTls = false,
|
||
RemoteCertificateValidationCallback certValidationCallback = null,
|
||
X509CertificateCollection clientCertificates = null)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(host))
|
||
{
|
||
throw new ArgumentException("Host cannot be null or empty.", nameof(host));
|
||
}
|
||
|
||
if (port <= 0 || port > 65535)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(port), "Port must be between 1 and 65535.");
|
||
}
|
||
|
||
_flows.Add(new SyslogTcpFlow(
|
||
host,
|
||
port,
|
||
batchSize,
|
||
minimumLevel,
|
||
backpressureStrategy,
|
||
useTls,
|
||
certValidationCallback,
|
||
clientCertificates));
|
||
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a Status Monitoring flow.
|
||
/// </summary>
|
||
public LogBuilder WriteToStatusFlow(
|
||
List<ServiceStatus> servicesToMonitor,
|
||
TimeSpan? checkInterval = null,
|
||
string statusDirectory = null,
|
||
Action<ServiceStatus> statusChangedTrigger = null)
|
||
{
|
||
_flows.Add(new StatusFlow(
|
||
servicesToMonitor,
|
||
checkInterval,
|
||
statusDirectory,
|
||
statusChangedTrigger));
|
||
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Syslog Tcp
|
||
/// </summary>
|
||
public LogBuilder WriteToSyslogUdp(
|
||
string host,
|
||
int port,
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
BackpressureStrategy backpressureStrategy = BackpressureStrategy.Wait)
|
||
{
|
||
_flows.Add(new SyslogUdpFlow(
|
||
host,
|
||
port,
|
||
batchSize,
|
||
minimumLevel,
|
||
backpressureStrategy));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Zabbix
|
||
/// </summary>
|
||
public LogBuilder WriteToZabbixFlow(
|
||
string host,
|
||
int port = 10051,
|
||
string zabbixHostname = null,
|
||
string zabbixKey = "log_event",
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
BackpressureStrategy backpressureStrategy = BackpressureStrategy.Wait)
|
||
{
|
||
_flows.Add(new ZabbixFlow(
|
||
host,
|
||
port,
|
||
zabbixHostname,
|
||
zabbixKey,
|
||
batchSize,
|
||
minimumLevel,
|
||
backpressureStrategy));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Graylog
|
||
/// </summary>
|
||
public LogBuilder WriteToGraylogFlow(
|
||
string host,
|
||
int port = 12201,
|
||
bool useTcp = false,
|
||
string graylogHostName = null,
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
BackpressureStrategy backpressureStrategy = BackpressureStrategy.Wait)
|
||
{
|
||
_flows.Add(new GraylogFlow(
|
||
host,
|
||
port,
|
||
useTcp,
|
||
graylogHostName,
|
||
batchSize,
|
||
minimumLevel,
|
||
backpressureStrategy));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Publishes log events to a Redis channel using the PUBLISH command (Pub/Sub)
|
||
/// and optionally appends them to a Redis List (LPUSH) for persistence.
|
||
///
|
||
/// Uses raw TCP + RESP protocol, so there arent additional dependencies
|
||
///
|
||
/// Features:
|
||
/// - Reconnect with exponential back-off on connection failure
|
||
/// - Optional LPUSH to a list key with LTRIM to cap list length
|
||
/// - Optional password authentication (AUTH command)
|
||
/// - Optional DB selection (SELECT command)
|
||
/// - Background writer thread (non-blocking callers)
|
||
/// </summary>
|
||
public LogBuilder RedisFlow(string host = "localhost",
|
||
int port = 6379,
|
||
string password = null,
|
||
int database = 0,
|
||
string channel = "eonacat:logs",
|
||
string listKey = null,
|
||
int maxListLength = 10000,
|
||
LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new RedisFlow(
|
||
host,
|
||
port,
|
||
password,
|
||
database,
|
||
channel,
|
||
listKey,
|
||
maxListLength,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Wraps any flow with token-bucket rate limiting and optional message deduplication.
|
||
/// Ideal for protecting high-latency sinks (email, Slack, HTTP) from log storms.
|
||
/// </summary>
|
||
/// <param name="inner">The downstream flow to protect.</param>
|
||
/// <param name="burstCapacity">
|
||
/// Max events that can be emitted in a burst per level (token bucket capacity).
|
||
/// </param>
|
||
/// <param name="refillPerSecond">
|
||
/// How many tokens are added per second per level. E.g. 5.0 = 5 events/second steady state.
|
||
/// </param>
|
||
/// <param name="deduplicate">
|
||
/// If true, identical messages within <paramref name="dedupWindow"/> are collapsed.
|
||
/// The suppressed count is appended to the message when the window expires.
|
||
/// </param>
|
||
/// <param name="dedupWindow">Deduplication window (default 60 s).</param>
|
||
/// <param name="dedupMaxKeys">Maximum number of distinct messages tracked (default 1000).</param>
|
||
/// <param name="minimumLevel">Minimum level this flow processes.</param>
|
||
public LogBuilder WriteToThrottled(IFlow inner,
|
||
int burstCapacity = 10,
|
||
double refillPerSecond = 1.0,
|
||
bool deduplicate = false,
|
||
TimeSpan dedupWindow = default(TimeSpan),
|
||
int dedupMaxKeys = 1000,
|
||
LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new ThrottledFlow(
|
||
inner,
|
||
burstCapacity,
|
||
refillPerSecond,
|
||
deduplicate,
|
||
dedupWindow,
|
||
dedupMaxKeys,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Splunk
|
||
/// </summary>
|
||
public LogBuilder WriteToSplunkFlow(
|
||
string splunkUrl,
|
||
string token,
|
||
string sourceType = "splunk_logs",
|
||
string hostName = null,
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
BackpressureStrategy backpressureStrategy = BackpressureStrategy.Wait)
|
||
{
|
||
_flows.Add(new SplunkFlow(
|
||
splunkUrl,
|
||
token,
|
||
sourceType,
|
||
hostName,
|
||
batchSize,
|
||
minimumLevel,
|
||
backpressureStrategy));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Pushes log events to a SignalR hub via HTTP POST to the hub's /send endpoint.
|
||
/// Works with ASP.NET SignalR (classic) and ASP.NET Core SignalR server-side REST API.
|
||
///
|
||
/// A lightweight alternative to the SignalR client library
|
||
///
|
||
/// On the server side you need a minimal hub endpoint that accepts POST:
|
||
/// POST {hubUrl}/send body: { "target": "...", "arguments": [ { log json } ] }
|
||
///
|
||
/// For live dashboards: the hub broadcasts to a "logs" group; clients subscribe and
|
||
/// render events in real time.
|
||
/// </summary>
|
||
public LogBuilder WriteToSignalR(
|
||
string hubUrl,
|
||
string hubMethod = "ReceiveLog",
|
||
HttpClient httpClient = null,
|
||
int batchSize = 20,
|
||
int batchIntervalMs = 500,
|
||
LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new SignalRFlow(
|
||
hubUrl,
|
||
hubMethod,
|
||
httpClient,
|
||
batchSize,
|
||
batchIntervalMs,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sends log events as HTML email digests via SMTP.
|
||
/// Batches events for <paramref name="digestMinutes"/> before sending,
|
||
/// unless flushOnCritical is true (Critical events bypass batching).
|
||
/// </summary>
|
||
public LogBuilder WriteToEmail(
|
||
string smtpHost,
|
||
int smtpPort = 587,
|
||
bool useSsl = true,
|
||
string username = null,
|
||
string password = null,
|
||
string from = null,
|
||
string to = null,
|
||
string subjectPrefix = "[EonaCatLogStack]",
|
||
int digestMinutes = 5,
|
||
bool flushOnCritical = true,
|
||
int maxEventsPerDigest = 100,
|
||
string headerName = null,
|
||
LogLevel minimumLevel = LogLevel.Error)
|
||
{
|
||
_flows.Add(new EmailFlow(
|
||
smtpHost,
|
||
smtpPort,
|
||
useSsl,
|
||
username,
|
||
password,
|
||
from,
|
||
to,
|
||
subjectPrefix,
|
||
digestMinutes,
|
||
flushOnCritical,
|
||
maxEventsPerDigest,
|
||
headerName,
|
||
minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Udp
|
||
/// </summary>
|
||
public LogBuilder WriteToUdp(
|
||
string host,
|
||
int port,
|
||
int flushIntervalInMilliseconds = 1000,
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
BackpressureStrategy backpressureStrategy = BackpressureStrategy.Wait)
|
||
{
|
||
_flows.Add(new UdpFlow(
|
||
host,
|
||
port,
|
||
flushIntervalInMilliseconds,
|
||
batchSize,
|
||
minimumLevel,
|
||
backpressureStrategy));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds in-memory buffer output
|
||
/// </summary>
|
||
public LogBuilder WriteToMemory(
|
||
int capacity = 10000,
|
||
LogLevel minimumLevel = LogLevel.Trace)
|
||
{
|
||
_flows.Add(new MemoryFlow(capacity, minimumLevel));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds HTTP endpoint output
|
||
/// </summary>
|
||
public LogBuilder WriteToHttp(
|
||
string endpoint,
|
||
HttpClient? httpClient = null,
|
||
int batchSize = 1,
|
||
LogLevel minimumLevel = LogLevel.Trace,
|
||
TimeSpan? batchInterval = null,
|
||
Dictionary<string, string>? headers = null)
|
||
{
|
||
_flows.Add(new HttpFlow(
|
||
endpoint,
|
||
httpClient,
|
||
batchSize,
|
||
minimumLevel,
|
||
batchInterval,
|
||
headers));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a custom flow
|
||
/// </summary>
|
||
public LogBuilder WriteTo(IFlow flow)
|
||
{
|
||
_flows.Add(flow ?? throw new ArgumentNullException(nameof(flow)));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with machine name
|
||
/// </summary>
|
||
public LogBuilder BoostWithMachineName()
|
||
{
|
||
_boosters.Add(new MachineNameBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with process ID
|
||
/// </summary>
|
||
public LogBuilder BoostWithProcessId()
|
||
{
|
||
_boosters.Add(new ProcessIdBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the current date (yyyy-MM-dd)
|
||
/// </summary>
|
||
public LogBuilder BoostWithDate()
|
||
{
|
||
_boosters.Add(new DateBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the current time (HH:mm:ss.fff)
|
||
/// </summary>
|
||
public LogBuilder BoostWithTime()
|
||
{
|
||
_boosters.Add(new TimeBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the current timestamp ticks
|
||
/// </summary>
|
||
public LogBuilder BoostWithTicks()
|
||
{
|
||
_boosters.Add(new TicksBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the process start time
|
||
/// </summary>
|
||
public LogBuilder BoostWithProcStart()
|
||
{
|
||
_boosters.Add(new ProcStartBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the uptime of the process in seconds
|
||
/// </summary>
|
||
public LogBuilder BoostWithUptime()
|
||
{
|
||
_boosters.Add(new UptimeBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the current thread name
|
||
/// </summary>
|
||
public LogBuilder BoostWithThreadName()
|
||
{
|
||
_boosters.Add(new ThreadNameBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with memory usage in MB
|
||
/// </summary>
|
||
public LogBuilder BoostWithMemory()
|
||
{
|
||
_boosters.Add(new MemoryBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the operating system description
|
||
/// </summary>
|
||
public LogBuilder BoostWithOS()
|
||
{
|
||
_boosters.Add(new OSBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the runtime/framework description
|
||
/// </summary>
|
||
public LogBuilder BoostWithFramework()
|
||
{
|
||
_boosters.Add(new FrameworkBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the application name and base directory
|
||
/// </summary>
|
||
public LogBuilder BoostWithApp()
|
||
{
|
||
_boosters.Add(new AppBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the current user name
|
||
/// </summary>
|
||
public LogBuilder BoostWithUser()
|
||
{
|
||
_boosters.Add(new UserBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with the current thread ID
|
||
/// </summary>
|
||
public LogBuilder BoostWithThreadId()
|
||
{
|
||
_boosters.Add(new ThreadIdBooster());
|
||
return this;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Boost logs with custom text
|
||
/// </summary>
|
||
public LogBuilder BoostWithCustomText(string key, string value)
|
||
{
|
||
_boosters.Add(new CustomTextBooster(key, value));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with environment name
|
||
/// </summary>
|
||
public LogBuilder BoostWithEnvironment(string environmentName)
|
||
{
|
||
_boosters.Add(new EnvironmentBooster(environmentName));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with application name and version
|
||
/// </summary>
|
||
public LogBuilder BoostWithApplication(string applicationName, string? version = null)
|
||
{
|
||
_boosters.Add(new ApplicationBooster(applicationName, version));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Boost logs with correlation ID from Activity
|
||
/// </summary>
|
||
public LogBuilder BoostWithCorrelationId()
|
||
{
|
||
_boosters.Add(new CorrelationIdBooster());
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a custom booster
|
||
/// </summary>
|
||
public LogBuilder Boost(IBooster booster)
|
||
{
|
||
_boosters.Add(booster ?? throw new ArgumentNullException(nameof(booster)));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a callback-based booster
|
||
/// </summary>
|
||
public LogBuilder Boost(string name, Func<Dictionary<string, object?>> callback)
|
||
{
|
||
_boosters.Add(new CallbackBooster(name, callback));
|
||
return this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Builds the configured logger
|
||
/// </summary>
|
||
public EonaCatLogStack Build()
|
||
{
|
||
var logger = new EonaCatLogStack(_category, _minimumLevel, _timestampMode);
|
||
logger.OnLog += (sender, message) => OnLog?.Invoke(sender, message);
|
||
|
||
foreach (var flow in _flows)
|
||
{
|
||
logger.AddFlow(flow);
|
||
}
|
||
|
||
foreach (var booster in _boosters)
|
||
{
|
||
logger.AddBooster(booster);
|
||
}
|
||
|
||
return logger;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a default logger with console and file output
|
||
/// </summary>
|
||
public static EonaCatLogStack CreateDefault(
|
||
string category = "Application",
|
||
string? logDirectory = null)
|
||
{
|
||
var directory = logDirectory ?? Path.Combine(AppContext.BaseDirectory, "logs");
|
||
|
||
return new LogBuilder(category)
|
||
.WithMinimumLevel(LogLevel.Information)
|
||
.WriteToConsole()
|
||
.WriteToFile(directory)
|
||
.BoostWithMachineName()
|
||
.BoostWithProcessId()
|
||
.Build();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get a flow by name
|
||
/// </summary>
|
||
/// <param name="name"></param>
|
||
public IFlow GetFlow(string name)
|
||
{
|
||
lock (_flows)
|
||
{
|
||
var flow = _flows.Find(x => x.Name == name);
|
||
return flow;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get a flow by type
|
||
/// </summary>
|
||
/// <param name="type"></param>
|
||
public IFlow GetFlow(Type type)
|
||
{
|
||
lock (_flows)
|
||
{
|
||
var flow = _flows.Find(x => x.GetType() == type);
|
||
return flow;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Add a flow to the logBuilder
|
||
/// </summary>
|
||
/// <param name="flow"></param>
|
||
public void AddFlow(IFlow flow)
|
||
{
|
||
lock (_flows)
|
||
{
|
||
_flows.Add(flow);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removes a flow from the logBuilder
|
||
/// </summary>
|
||
/// <param name="flow">To be removed flow</param>
|
||
public void RemoveFlow(IFlow flow)
|
||
{
|
||
lock (_flows)
|
||
{
|
||
_flows.Remove(flow);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removes a flow from the logBuilder by name
|
||
/// </summary>
|
||
/// <param name="name">To be removed flow name</param>
|
||
public void RemoveFlow(string name)
|
||
{
|
||
if (name == null)
|
||
{
|
||
throw new ArgumentNullException("name");
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(name))
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (_flows) { _flows.RemoveAll(f => f.Name == name); }
|
||
}
|
||
} |