Added max folder capacity
Added some methods in the EncryptedFileFlow Added filters for FileFlow
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
using EonaCat.Json;
|
||||
using EonaCat.LogStack.Core;
|
||||
using EonaCat.LogStack.Core;
|
||||
using EonaCat.LogStack.EonaCatLogStackCore;
|
||||
using EonaCat.LogStack.EonaCatLogStackCore.Policies;
|
||||
using System;
|
||||
@@ -42,6 +41,10 @@ namespace EonaCat.LogStack.Flows
|
||||
{ LogLevel.Critical, "CRITICAL" },
|
||||
};
|
||||
|
||||
private static readonly char[] CsvSpecialChars = { ',', '"', '\n', '\r' };
|
||||
private static readonly string CachedMachineName = Environment.MachineName;
|
||||
private static readonly int CachedPid = Process.GetCurrentProcess().Id;
|
||||
|
||||
private const string CsvHeader = "timestamp,level,category,message,exception,properties\r\n";
|
||||
|
||||
private readonly BlockingCollection<LogEvent> _queue;
|
||||
@@ -57,6 +60,7 @@ namespace EonaCat.LogStack.Flows
|
||||
private readonly string _directory;
|
||||
private readonly string _filePrefix;
|
||||
private readonly long _maxFileSize;
|
||||
private readonly long _maxDirectorySize;
|
||||
private readonly FileRetentionPolicy _retention;
|
||||
private readonly TimestampMode _timestampMode;
|
||||
private readonly TimeSpan _flushInterval;
|
||||
@@ -74,11 +78,38 @@ namespace EonaCat.LogStack.Flows
|
||||
private readonly object _secondaryWritersLock = new object();
|
||||
private int _correlationSeed;
|
||||
|
||||
private readonly List<Func<LogEvent, bool>> _filters = new List<Func<LogEvent, bool>>();
|
||||
private readonly object _filtersLock = new object();
|
||||
|
||||
private readonly ConcurrentDictionary<string, long> _deduplicationCache
|
||||
= new ConcurrentDictionary<string, long>(StringComparer.Ordinal);
|
||||
private TimeSpan _deduplicationWindow = TimeSpan.Zero;
|
||||
private volatile bool _deduplicationEnabled;
|
||||
|
||||
private string _dateFormat = "yyyyMMdd";
|
||||
private volatile Exception _lastError;
|
||||
private long _lastErrorTimestamp;
|
||||
private long _totalErrors;
|
||||
|
||||
private readonly List<KeyValuePair<string, Func<LogEvent, object>>> _enrichers
|
||||
= new List<KeyValuePair<string, Func<LogEvent, object>>>();
|
||||
|
||||
private long _currentMemoryBytes;
|
||||
|
||||
// Rate limiting
|
||||
private int _maxEventsPerSecond;
|
||||
private bool _rateLimitEnabled => _maxEventsPerSecond > 0;
|
||||
private long _rateLimitWindowStart;
|
||||
private int _rateLimitCounter;
|
||||
private readonly object _rateLimitLock = new object();
|
||||
|
||||
// Auto-flush on error
|
||||
private volatile bool _autoFlushOnError;
|
||||
|
||||
// Scoped properties (AsyncLocal for ambient context)
|
||||
private static readonly AsyncLocal<Dictionary<string, object>> _scopeProperties
|
||||
= new AsyncLocal<Dictionary<string, object>>();
|
||||
|
||||
private long _totalBytesWritten;
|
||||
private long _totalRotations;
|
||||
|
||||
@@ -121,6 +152,7 @@ namespace EonaCat.LogStack.Flows
|
||||
string directory,
|
||||
string filePrefix = "log",
|
||||
long maxFileSize = 200 * 1024 * 1024,
|
||||
long maxDirectorySize = 2L * 1024 * 1024 * 1024,
|
||||
FileRetentionPolicy retention = null,
|
||||
int flushIntervalMs = 2000,
|
||||
int batchSize = 1,
|
||||
@@ -157,6 +189,7 @@ namespace EonaCat.LogStack.Flows
|
||||
_filePrefix = filePrefix;
|
||||
_template = template;
|
||||
_maxFileSize = maxFileSize;
|
||||
_maxDirectorySize = maxDirectorySize > 0 ? maxDirectorySize : 10L * maxFileSize;
|
||||
_retention = retention ?? new FileRetentionPolicy();
|
||||
_timestampMode = timestampMode;
|
||||
_useCategoryRouting = useCategoryRouting;
|
||||
@@ -318,6 +351,176 @@ namespace EonaCat.LogStack.Flows
|
||||
MinimumLevel = level;
|
||||
}
|
||||
|
||||
/// <summary>Add a custom filter predicate. Events are logged only if ALL filters return true.</summary>
|
||||
public FileFlow WithFilter(Func<LogEvent, bool> predicate)
|
||||
{
|
||||
if (predicate == null)
|
||||
{
|
||||
throw new ArgumentNullException("predicate");
|
||||
}
|
||||
|
||||
lock (_filtersLock)
|
||||
{
|
||||
_filters.Add(predicate);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Enable deduplication: suppress identical messages within the given time window.</summary>
|
||||
public FileFlow WithDeduplication(TimeSpan window)
|
||||
{
|
||||
if (window <= TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("window", "Deduplication window must be positive.");
|
||||
}
|
||||
|
||||
_deduplicationWindow = window;
|
||||
_deduplicationEnabled = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Configure a custom date format for log file names (default: yyyyMMdd).</summary>
|
||||
public FileFlow WithDateFormat(string dateFormat)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dateFormat))
|
||||
{
|
||||
throw new ArgumentNullException("dateFormat");
|
||||
}
|
||||
|
||||
_dateFormat = dateFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Limit the flow to a maximum number of events per second. Events exceeding the limit are dropped.</summary>
|
||||
public FileFlow WithRateLimit(int maxEventsPerSecond)
|
||||
{
|
||||
_maxEventsPerSecond = maxEventsPerSecond;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>When enabled, the file stream is flushed immediately after writing Error or Critical level events.</summary>
|
||||
public FileFlow WithAutoFlushOnError(bool enabled = true)
|
||||
{
|
||||
_autoFlushOnError = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Push scoped properties that will be included in all log events written on the current async context.</summary>
|
||||
public IDisposable BeginScope(params KeyValuePair<string, object>[] properties)
|
||||
{
|
||||
var previous = _scopeProperties.Value;
|
||||
var merged = previous != null
|
||||
? new Dictionary<string, object>(previous)
|
||||
: new Dictionary<string, object>();
|
||||
|
||||
foreach (var kv in properties)
|
||||
{
|
||||
merged[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
_scopeProperties.Value = merged;
|
||||
return new ScopeDisposable(previous);
|
||||
}
|
||||
|
||||
/// <summary>Push a single scoped property.</summary>
|
||||
public IDisposable BeginScope(string key, object value)
|
||||
{
|
||||
return BeginScope(new KeyValuePair<string, object>(key, value));
|
||||
}
|
||||
|
||||
/// <summary>Returns the current queue depth (number of pending events).</summary>
|
||||
public int GetQueueDepth()
|
||||
{
|
||||
return _queue.Count;
|
||||
}
|
||||
|
||||
/// <summary>Returns the current estimated memory usage of the queue in bytes.</summary>
|
||||
public long GetMemoryPressureBytes()
|
||||
{
|
||||
return Interlocked.Read(ref _currentMemoryBytes);
|
||||
}
|
||||
|
||||
/// <summary>Generates a fingerprint hash for an exception to assist with grouping.</summary>
|
||||
public static string GetExceptionFingerprint(Exception ex)
|
||||
{
|
||||
if (ex == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string source = string.Concat(
|
||||
ex.GetType().FullName, "|",
|
||||
ex.TargetSite?.Name ?? string.Empty, "|",
|
||||
ex.StackTrace != null && ex.StackTrace.Length > 0
|
||||
? ex.StackTrace.Substring(0, Math.Min(200, ex.StackTrace.Length))
|
||||
: string.Empty);
|
||||
|
||||
// Simple FNV-1a hash
|
||||
unchecked
|
||||
{
|
||||
uint hash = 2166136261;
|
||||
foreach (char c in source)
|
||||
{
|
||||
hash ^= c;
|
||||
hash *= 16777619;
|
||||
}
|
||||
return hash.ToString("x8");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ScopeDisposable : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, object> _previous;
|
||||
|
||||
public ScopeDisposable(Dictionary<string, object> previous)
|
||||
{
|
||||
_previous = previous;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_scopeProperties.Value = _previous;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns true if the flow is healthy (no recent errors and writer thread alive).</summary>
|
||||
public bool IsHealthy()
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_writerThread.IsAlive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
long lastErr = Interlocked.Read(ref _lastErrorTimestamp);
|
||||
if (lastErr > 0)
|
||||
{
|
||||
TimeSpan since = TimeSpan.FromTicks(DateTime.UtcNow.Ticks - lastErr);
|
||||
if (since < TimeSpan.FromMinutes(1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Returns the last error encountered by the writer, or null if none.</summary>
|
||||
public Exception GetLastError()
|
||||
{
|
||||
return _lastError;
|
||||
}
|
||||
|
||||
/// <summary>Returns the total number of write errors encountered.</summary>
|
||||
public long GetTotalErrors()
|
||||
{
|
||||
return Interlocked.Read(ref _totalErrors);
|
||||
}
|
||||
|
||||
/// <summary>Returns live throughput and health metrics.</summary>
|
||||
public LogStats GetStats()
|
||||
{
|
||||
@@ -347,6 +550,28 @@ namespace EonaCat.LogStack.Flows
|
||||
return Task.FromResult(WriteResult.LevelFiltered);
|
||||
}
|
||||
|
||||
if (!PassesFilters(logEvent))
|
||||
{
|
||||
return Task.FromResult(WriteResult.LevelFiltered);
|
||||
}
|
||||
|
||||
if (_deduplicationEnabled && IsDuplicate(logEvent))
|
||||
{
|
||||
return Task.FromResult(WriteResult.LevelFiltered);
|
||||
}
|
||||
|
||||
if (_rateLimitEnabled && !TryPassRateLimit())
|
||||
{
|
||||
Interlocked.Increment(ref DroppedCount);
|
||||
Action<LogEvent> drop = _onDrop;
|
||||
if (drop != null)
|
||||
{
|
||||
drop(logEvent);
|
||||
}
|
||||
|
||||
return Task.FromResult(WriteResult.Dropped);
|
||||
}
|
||||
|
||||
return Task.FromResult(TryEnqueue(logEvent));
|
||||
}
|
||||
|
||||
@@ -361,9 +586,11 @@ namespace EonaCat.LogStack.Flows
|
||||
|
||||
WriteResult result = WriteResult.Success;
|
||||
SamplingPolicy sp = _samplingPolicy;
|
||||
ReadOnlySpan<LogEvent> span = logEvents.Span;
|
||||
|
||||
foreach (LogEvent e in logEvents.ToArray())
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
LogEvent e = span[i];
|
||||
if (e.Level < MinimumLevel)
|
||||
{
|
||||
continue;
|
||||
@@ -374,6 +601,29 @@ namespace EonaCat.LogStack.Flows
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PassesFilters(e))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_deduplicationEnabled && IsDuplicate(e))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_rateLimitEnabled && !TryPassRateLimit())
|
||||
{
|
||||
Interlocked.Increment(ref DroppedCount);
|
||||
Action<LogEvent> drop = _onDrop;
|
||||
if (drop != null)
|
||||
{
|
||||
drop(e);
|
||||
}
|
||||
|
||||
result = WriteResult.Dropped;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryEnqueue(e) == WriteResult.Dropped)
|
||||
{
|
||||
result = WriteResult.Dropped;
|
||||
@@ -418,6 +668,88 @@ namespace EonaCat.LogStack.Flows
|
||||
return WriteResult.Success;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool TryPassRateLimit()
|
||||
{
|
||||
long now = DateTime.UtcNow.Ticks;
|
||||
lock (_rateLimitLock)
|
||||
{
|
||||
long elapsed = now - _rateLimitWindowStart;
|
||||
if (elapsed >= TimeSpan.TicksPerSecond)
|
||||
{
|
||||
_rateLimitWindowStart = now;
|
||||
_rateLimitCounter = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_rateLimitEnabled && _rateLimitCounter >= _maxEventsPerSecond)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_rateLimitCounter++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool PassesFilters(LogEvent log)
|
||||
{
|
||||
lock (_filtersLock)
|
||||
{
|
||||
for (int i = 0; i < _filters.Count; i++)
|
||||
{
|
||||
if (!_filters[i](log))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsDuplicate(LogEvent log)
|
||||
{
|
||||
string key = string.Concat(
|
||||
log.Level.ToString(), "|",
|
||||
log.Category ?? string.Empty, "|",
|
||||
log.Message.Length > 0 ? log.Message.ToString() : string.Empty);
|
||||
|
||||
long nowTicks = DateTime.UtcNow.Ticks;
|
||||
long windowTicks = _deduplicationWindow.Ticks;
|
||||
|
||||
long existing;
|
||||
if (_deduplicationCache.TryGetValue(key, out existing))
|
||||
{
|
||||
if (nowTicks - existing < windowTicks)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_deduplicationCache[key] = nowTicks;
|
||||
|
||||
// Periodic cleanup: remove expired entries when cache grows large
|
||||
if (_deduplicationCache.Count > 10000)
|
||||
{
|
||||
CleanDeduplicationCache(nowTicks, windowTicks);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CleanDeduplicationCache(long nowTicks, long windowTicks)
|
||||
{
|
||||
foreach (var kvp in _deduplicationCache)
|
||||
{
|
||||
if (nowTicks - kvp.Value >= windowTicks)
|
||||
{
|
||||
long removed;
|
||||
_deduplicationCache.TryRemove(kvp.Key, out removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
@@ -642,6 +974,9 @@ namespace EonaCat.LogStack.Flows
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_lastError = ex;
|
||||
Interlocked.Increment(ref _totalErrors);
|
||||
Interlocked.Exchange(ref _lastErrorTimestamp, DateTime.UtcNow.Ticks);
|
||||
try
|
||||
{
|
||||
WriteToConsoleError("[FileFlow] Write error for '" + path + "': " + ex.Message);
|
||||
@@ -651,6 +986,19 @@ namespace EonaCat.LogStack.Flows
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _totalBytesWritten, line.Length + 1);
|
||||
|
||||
// Auto-flush on error/critical
|
||||
if (_autoFlushOnError && (log.Level >= LogLevel.Error))
|
||||
{
|
||||
lock (_fileLock)
|
||||
{
|
||||
OpenFile autoFlushOf;
|
||||
if (_openFiles.TryGetValue(path, out autoFlushOf))
|
||||
{
|
||||
try { autoFlushOf.Writer.Flush(); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -719,11 +1067,30 @@ namespace EonaCat.LogStack.Flows
|
||||
sb.Append("\",");
|
||||
|
||||
sb.Append("\"host\":\"");
|
||||
AppendJsonEscaped(Environment.MachineName, sb);
|
||||
AppendJsonEscaped(CachedMachineName, sb);
|
||||
sb.Append("\",");
|
||||
|
||||
sb.Append("\"pid\":");
|
||||
sb.Append(Process.GetCurrentProcess().Id);
|
||||
sb.Append(CachedPid);
|
||||
sb.Append(',');
|
||||
|
||||
// Distributed tracing
|
||||
if (log.TraceId != default(ActivityTraceId))
|
||||
{
|
||||
sb.Append("\"traceId\":\"");
|
||||
sb.Append(log.TraceId.ToHexString());
|
||||
sb.Append("\",");
|
||||
}
|
||||
|
||||
if (log.SpanId != default(ActivitySpanId))
|
||||
{
|
||||
sb.Append("\"spanId\":\"");
|
||||
sb.Append(log.SpanId.ToHexString());
|
||||
sb.Append("\",");
|
||||
}
|
||||
|
||||
sb.Append("\"threadId\":");
|
||||
sb.Append(log.ThreadId);
|
||||
sb.Append(',');
|
||||
}
|
||||
|
||||
@@ -777,7 +1144,7 @@ namespace EonaCat.LogStack.Flows
|
||||
sb.Append('"');
|
||||
}
|
||||
|
||||
foreach (var property in log.Properties.ToArray())
|
||||
foreach (var property in log.Properties)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
@@ -927,7 +1294,7 @@ namespace EonaCat.LogStack.Flows
|
||||
sb.Append("</property>");
|
||||
}
|
||||
|
||||
foreach (var property in log.Properties.ToArray())
|
||||
foreach (var property in log.Properties)
|
||||
{
|
||||
if (string.IsNullOrEmpty(property.Key))
|
||||
{
|
||||
@@ -1025,7 +1392,7 @@ namespace EonaCat.LogStack.Flows
|
||||
object val = kv.Value(log);
|
||||
AppendCsvInner(val != null ? val.ToString() : "null", sb);
|
||||
}
|
||||
foreach (var property in log.Properties.ToArray())
|
||||
foreach (var property in log.Properties)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
@@ -1047,7 +1414,7 @@ namespace EonaCat.LogStack.Flows
|
||||
value = string.Empty;
|
||||
}
|
||||
|
||||
bool needsQuote = value.IndexOfAny(new[] { ',', '"', '\n', '\r' }) >= 0;
|
||||
bool needsQuote = value.IndexOfAny(CsvSpecialChars) >= 0;
|
||||
if (needsQuote)
|
||||
{
|
||||
sb.Append('"');
|
||||
@@ -1253,7 +1620,20 @@ namespace EonaCat.LogStack.Flows
|
||||
for (int i = maxFiles - 1; i >= 1; i--)
|
||||
{
|
||||
string src = Path.Combine(dir, baseName + "_" + i + _fileExtension);
|
||||
string srcGz = src + ".gz";
|
||||
string dst = Path.Combine(dir, baseName + "_" + (i + 1) + _fileExtension);
|
||||
string dstGz = dst + ".gz";
|
||||
|
||||
// Move .gz variant if it exists
|
||||
if (File.Exists(srcGz))
|
||||
{
|
||||
if (File.Exists(dstGz))
|
||||
{
|
||||
File.Delete(dstGz);
|
||||
}
|
||||
File.Move(srcGz, dstGz);
|
||||
}
|
||||
|
||||
if (!File.Exists(src))
|
||||
{
|
||||
continue;
|
||||
@@ -1261,8 +1641,6 @@ namespace EonaCat.LogStack.Flows
|
||||
|
||||
if (File.Exists(dst))
|
||||
{
|
||||
_compressionQueue.Enqueue(dst);
|
||||
_compressionSignal.Release(1);
|
||||
File.Delete(dst);
|
||||
}
|
||||
File.Move(src, dst);
|
||||
@@ -1275,6 +1653,14 @@ namespace EonaCat.LogStack.Flows
|
||||
}
|
||||
|
||||
File.Move(filePath, archive);
|
||||
|
||||
// Queue the archived file for compression
|
||||
if (_compressionFormat != CompressionFormat.None)
|
||||
{
|
||||
_compressionQueue.Enqueue(archive);
|
||||
_compressionSignal.Release(1);
|
||||
}
|
||||
|
||||
return archive;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1343,6 +1729,9 @@ namespace EonaCat.LogStack.Flows
|
||||
gz.Write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the original uncompressed file after successful compression
|
||||
try { File.Delete(path); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
private void PeriodicFlushLoop()
|
||||
@@ -1361,16 +1750,12 @@ namespace EonaCat.LogStack.Flows
|
||||
break;
|
||||
}
|
||||
|
||||
OpenFile[] snapshot;
|
||||
lock (_fileLock)
|
||||
{
|
||||
snapshot = new OpenFile[_openFiles.Count];
|
||||
_openFiles.Values.CopyTo(snapshot, 0);
|
||||
}
|
||||
|
||||
foreach (OpenFile of in snapshot)
|
||||
{
|
||||
try { of.Writer.Flush(); } catch { /* ignore */ }
|
||||
foreach (OpenFile of in _openFiles.Values)
|
||||
{
|
||||
try { of.Writer.Flush(); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException) { break; }
|
||||
@@ -1405,6 +1790,7 @@ namespace EonaCat.LogStack.Flows
|
||||
}
|
||||
|
||||
FileInfo[] files = dir.GetFiles("*" + _fileExtension)
|
||||
.Concat(dir.GetFiles("*" + _fileExtension + ".gz"))
|
||||
.OrderByDescending(f => f.LastWriteTimeUtc)
|
||||
.ToArray();
|
||||
|
||||
@@ -1418,8 +1804,9 @@ namespace EonaCat.LogStack.Flows
|
||||
bool tooMany = _retention.MaxRolledFiles > 0 && kept >= _retention.MaxRolledFiles;
|
||||
bool tooLarge = _retention.MaxTotalArchiveBytes > 0
|
||||
&& totalBytes + f.Length > _retention.MaxTotalArchiveBytes;
|
||||
bool directoryTooLarge = totalBytes + f.Length > _maxDirectorySize;
|
||||
|
||||
if (tooOld || tooMany || tooLarge)
|
||||
if (tooOld || tooMany || tooLarge || directoryTooLarge)
|
||||
{
|
||||
try { f.Delete(); } catch { /* ignore */ }
|
||||
}
|
||||
@@ -1460,7 +1847,7 @@ namespace EonaCat.LogStack.Flows
|
||||
|
||||
return Path.Combine(
|
||||
_directory,
|
||||
prefix + "_" + Environment.MachineName + "_" + date.ToString("yyyyMMdd") + _fileExtension);
|
||||
string.Concat(prefix, "_", CachedMachineName, "_", date.ToString(_dateFormat), _fileExtension));
|
||||
}
|
||||
|
||||
private void SetFileExtension(FileOutputFormat fmt)
|
||||
@@ -1488,7 +1875,7 @@ namespace EonaCat.LogStack.Flows
|
||||
+ log.Properties.Count * 40;
|
||||
if (log.Exception != null)
|
||||
{
|
||||
s += log.Exception.ToString().Length * 2;
|
||||
s += 2048; // Avoid calling Exception.ToString() just for size estimation
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -1541,7 +1928,7 @@ namespace EonaCat.LogStack.Flows
|
||||
sb.Append(_timestampMode == TimestampMode.Local
|
||||
? TimeZoneInfo.Local.StandardName : "UTC");
|
||||
case "host":
|
||||
return (log, sb) => sb.Append(Environment.MachineName);
|
||||
return (log, sb) => sb.Append(CachedMachineName);
|
||||
case "category":
|
||||
return (log, sb) => { if (log.Category != null) { sb.Append(log.Category); } };
|
||||
case "thread":
|
||||
@@ -1573,7 +1960,23 @@ namespace EonaCat.LogStack.Flows
|
||||
case "newline":
|
||||
return (log, sb) => sb.AppendLine();
|
||||
case "pid":
|
||||
return (log, sb) => sb.Append(Process.GetCurrentProcess().Id);
|
||||
return (log, sb) => sb.Append(CachedPid);
|
||||
case "traceid":
|
||||
return (log, sb) =>
|
||||
{
|
||||
if (log.TraceId != default(ActivityTraceId))
|
||||
{
|
||||
sb.Append(log.TraceId.ToHexString());
|
||||
}
|
||||
};
|
||||
case "spanid":
|
||||
return (log, sb) =>
|
||||
{
|
||||
if (log.SpanId != default(ActivitySpanId))
|
||||
{
|
||||
sb.Append(log.SpanId.ToHexString());
|
||||
}
|
||||
};
|
||||
default:
|
||||
return BuildCustomOrLiteralToken(token);
|
||||
}
|
||||
@@ -1598,9 +2001,11 @@ namespace EonaCat.LogStack.Flows
|
||||
|
||||
private void AppendProperties(LogEvent log, StringBuilder sb)
|
||||
{
|
||||
var scopeProps = _scopeProperties.Value;
|
||||
bool hasEnrichers = _enrichers.Count > 0;
|
||||
bool hasProps = log.Properties.Count > 0;
|
||||
if (!hasEnrichers && !hasProps)
|
||||
bool hasScope = scopeProps != null && scopeProps.Count > 0;
|
||||
if (!hasEnrichers && !hasProps && !hasScope)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1620,7 +2025,7 @@ namespace EonaCat.LogStack.Flows
|
||||
sb.Append(kv.Key).Append('=').Append(val != null ? val.ToString() : "null");
|
||||
}
|
||||
|
||||
foreach (var property in log.Properties.ToArray())
|
||||
foreach (var property in log.Properties)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
@@ -1632,6 +2037,21 @@ namespace EonaCat.LogStack.Flows
|
||||
.Append(property.Value != null ? property.Value.ToString() : "null");
|
||||
}
|
||||
|
||||
if (hasScope)
|
||||
{
|
||||
foreach (var kv in scopeProps)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
first = false;
|
||||
sb.Append(kv.Key).Append('=')
|
||||
.Append(kv.Value != null ? kv.Value.ToString() : "null");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append('}');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user