Updated
This commit is contained in:
+31
-16
@@ -7,28 +7,43 @@ using EonaCat.LogStack.Core;
|
|||||||
var logger = new LogBuilder("MyApp")
|
var logger = new LogBuilder("MyApp")
|
||||||
.WithMinimumLevel(LogLevel.Information)
|
.WithMinimumLevel(LogLevel.Information)
|
||||||
.WriteToConsole()
|
.WriteToConsole()
|
||||||
.WriteToFile("C:\\tesss", maxFileSize: 50 * 1024 * 1024)
|
.WriteToFile("./testlogs", maxFileSize: 50 * 1024 * 1024)
|
||||||
//.WriteToJsonFile("./logs", maxFileSize: 50 * 1024 * 1024)
|
|
||||||
//.WriteToHttp("https://127.0.0.1")
|
|
||||||
//.WriteToUdp("127.0.0.1", 514)
|
|
||||||
//.WriteToTcp("127.0.0.1", 514)
|
|
||||||
//.WriteToDatabase(null)
|
|
||||||
//.WriteToDiscord("https://discord.com/api/webhooks/...")
|
|
||||||
//.WriteToMicrosoftTeams("https://outlook.office.com/webhook/...")
|
|
||||||
//.WriteToElasticSearch("http://localhost:9200/logs")
|
|
||||||
//.WriteToGraylogFlow(null)
|
|
||||||
//.WriteToZabbixFlow(null)
|
|
||||||
.BoostWithCorrelationId()
|
.BoostWithCorrelationId()
|
||||||
.BoostWithProcessId()
|
.BoostWithProcessId()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
Console.WriteLine("Logger created successfully");
|
||||||
|
Console.WriteLine("Writing log entries...");
|
||||||
|
|
||||||
|
// Test: Write a few log entries and then dispose
|
||||||
while (true)
|
for (int i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
logger.Information("Application started");
|
logger.Information("Application started - iteration " + i);
|
||||||
logger.Error(new Exception("Nerd!"), "Something went wrong");
|
logger.Error(new Exception("Nerd!"), "Something went wrong - iteration " + i);
|
||||||
await Task.Delay(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Wrote 10 log entries");
|
||||||
|
|
||||||
|
// Wait a bit for the flush to happen
|
||||||
|
await Task.Delay(3000);
|
||||||
|
|
||||||
|
Console.WriteLine("Disposing logger...");
|
||||||
await logger.DisposeAsync(); // Flushes all logs
|
await logger.DisposeAsync(); // Flushes all logs
|
||||||
|
|
||||||
|
Console.WriteLine("Logging complete. Check ./testlogs for log files.");
|
||||||
|
|
||||||
|
// List files that were created
|
||||||
|
var logdir = new System.IO.DirectoryInfo("./testlogs");
|
||||||
|
if (logdir.Exists)
|
||||||
|
{
|
||||||
|
var files = logdir.GetFiles();
|
||||||
|
Console.WriteLine($"Found {files.Length} log files:");
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - {file.Name} ({file.Length} bytes)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("./testlogs directory does not exist!");
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
|
|||||||
<Copyright>EonaCat (Jeroen Saey)</Copyright>
|
<Copyright>EonaCat (Jeroen Saey)</Copyright>
|
||||||
<PackageTags>EonaCat;Logger;EonaCatLogStack;Log;Writer;Flows;LogStack;Memory;Speed;Jeroen;Saey</PackageTags>
|
<PackageTags>EonaCat;Logger;EonaCatLogStack;Log;Writer;Flows;LogStack;Memory;Speed;Jeroen;Saey</PackageTags>
|
||||||
<PackageIconUrl />
|
<PackageIconUrl />
|
||||||
<FileVersion>0.1.4</FileVersion>
|
<FileVersion>0.1.6</FileVersion>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
@@ -25,7 +25,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<EVRevisionFormat>0.1.4+{chash:10}.{c:ymd}</EVRevisionFormat>
|
<EVRevisionFormat>0.1.6+{chash:10}.{c:ymd}</EVRevisionFormat>
|
||||||
<EVDefault>true</EVDefault>
|
<EVDefault>true</EVDefault>
|
||||||
<EVInfo>true</EVInfo>
|
<EVInfo>true</EVInfo>
|
||||||
<EVTagMatch>v[0-9]*</EVTagMatch>
|
<EVTagMatch>v[0-9]*</EVTagMatch>
|
||||||
@@ -36,7 +36,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>0.1.4</Version>
|
<Version>0.1.6</Version>
|
||||||
<PackageId>EonaCat.LogStack</PackageId>
|
<PackageId>EonaCat.LogStack</PackageId>
|
||||||
<Product>EonaCat.LogStack</Product>
|
<Product>EonaCat.LogStack</Product>
|
||||||
<RepositoryUrl>https://git.saey.me/EonaCat/EonaCat.LogStack</RepositoryUrl>
|
<RepositoryUrl>https://git.saey.me/EonaCat/EonaCat.LogStack</RepositoryUrl>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -200,6 +201,18 @@ namespace EonaCat.LogStack.Flows
|
|||||||
_outputFormat = outputFormat;
|
_outputFormat = outputFormat;
|
||||||
_compressionFormat = compression;
|
_compressionFormat = compression;
|
||||||
|
|
||||||
|
// Use the cascading fallback strategy to resolve the final logging directory
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_directory = ResolveLoggingDirectory(_directory);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WriteToConsoleError($"[FileFlow] Critical error in directory resolution: {ex.Message}");
|
||||||
|
_directory = Path.GetTempPath();
|
||||||
|
OnDirectoryException?.Invoke(this, $"FileFlow: Critical failure in directory resolution: {ex.Message}. Falling back to temp: '{_directory}'");
|
||||||
|
}
|
||||||
|
|
||||||
_logLevelsForSeparateFiles = logLevelsForSeparateFiles != null
|
_logLevelsForSeparateFiles = logLevelsForSeparateFiles != null
|
||||||
? new HashSet<LogLevel>(logLevelsForSeparateFiles)
|
? new HashSet<LogLevel>(logLevelsForSeparateFiles)
|
||||||
: new HashSet<LogLevel>();
|
: new HashSet<LogLevel>();
|
||||||
@@ -207,58 +220,13 @@ namespace EonaCat.LogStack.Flows
|
|||||||
SetFileExtension(outputFormat);
|
SetFileExtension(outputFormat);
|
||||||
CompileTemplate(template);
|
CompileTemplate(template);
|
||||||
|
|
||||||
// Resolve relative path
|
|
||||||
if (_directory.StartsWith("./", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _directory.Substring(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Never allow logging initialization to crash the host because of a bad
|
|
||||||
// directory (ACLs, antivirus locks, read-only locations, etc.).
|
|
||||||
// Fall back to a per-user temp directory.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Use DirectoryPermissionHelper to ensure directory with proper permissions
|
|
||||||
if (!Helpers.DirectoryPermissionHelper.EnsureDirectoryHierarchy(_directory))
|
|
||||||
{
|
|
||||||
throw new UnauthorizedAccessException($"Cannot write to directory: {Helpers.DirectoryPermissionHelper.GetAccessIssueDiagnosis(_directory)}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var processId = Process.GetCurrentProcess().Id;
|
|
||||||
var newDirectory = Path.Combine(Path.GetTempPath(), "EonaCat.LogStack", processId.ToString());
|
|
||||||
|
|
||||||
if (Helpers.DirectoryPermissionHelper.EnsureDirectoryHierarchy(newDirectory))
|
|
||||||
{
|
|
||||||
OnDirectoryException?.Invoke(this, $"FileFlow: Could not create directory: '{_directory}' ({ex.Message}), using directory '{newDirectory}' instead");
|
|
||||||
_directory = newDirectory;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Failed to create fallback directory: {newDirectory}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
var newDirectory = Path.GetTempPath();
|
|
||||||
OnDirectoryException?.Invoke(this, $"FileFlow: Could not create any writable directory, falling back to temp: '{newDirectory}'. Original error: {ex.Message}");
|
|
||||||
_directory = newDirectory;
|
|
||||||
|
|
||||||
// Last resort: disable file output by pointing to temp path.
|
|
||||||
// The writer thread still runs and swallows failures.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockingCollection with bounded capacity
|
// BlockingCollection with bounded capacity
|
||||||
_queue = new BlockingCollection<LogEvent>(new ConcurrentQueue<LogEvent>(), QueueCapacity);
|
_queue = new BlockingCollection<LogEvent>(new ConcurrentQueue<LogEvent>(), QueueCapacity);
|
||||||
|
|
||||||
// Dedicated writer thread
|
// Dedicated writer thread
|
||||||
_writerThread = new Thread(WriterThreadBody)
|
_writerThread = new Thread(WriterThreadBody)
|
||||||
{
|
{
|
||||||
IsBackground = true,
|
IsBackground = false,
|
||||||
Name = "FileFlow.Writer[" + filePrefix + "]",
|
Name = "FileFlow.Writer[" + filePrefix + "]",
|
||||||
Priority = ThreadPriority.AboveNormal,
|
Priority = ThreadPriority.AboveNormal,
|
||||||
};
|
};
|
||||||
@@ -830,6 +798,14 @@ namespace EonaCat.LogStack.Flows
|
|||||||
_isDisposing = true; // Signal all threads that disposal is in progress
|
_isDisposing = true; // Signal all threads that disposal is in progress
|
||||||
|
|
||||||
_queue.CompleteAdding();
|
_queue.CompleteAdding();
|
||||||
|
|
||||||
|
// Give the writer thread time to drain the queue before canceling
|
||||||
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
while (_queue.Count > 0 && sw.Elapsed < TimeSpan.FromSeconds(5))
|
||||||
|
{
|
||||||
|
Thread.Sleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
_cts.Cancel();
|
_cts.Cancel();
|
||||||
|
|
||||||
// Give threads more time to gracefully exit (increased from 2000ms to 5000ms)
|
// Give threads more time to gracefully exit (increased from 2000ms to 5000ms)
|
||||||
@@ -873,6 +849,7 @@ namespace EonaCat.LogStack.Flows
|
|||||||
}
|
}
|
||||||
private void WriterThreadBody()
|
private void WriterThreadBody()
|
||||||
{
|
{
|
||||||
|
WriteToConsoleError("[FileFlow] Writer thread started");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!_queue.IsCompleted)
|
while (!_queue.IsCompleted)
|
||||||
@@ -940,6 +917,8 @@ namespace EonaCat.LogStack.Flows
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Write to both stdout and stderr to ensure visibility
|
||||||
|
Console.WriteLine(text);
|
||||||
Console.Error.WriteLine(text);
|
Console.Error.WriteLine(text);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -1015,12 +994,28 @@ namespace EonaCat.LogStack.Flows
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EnsureFileOpen(path, log);
|
if (!EnsureFileOpen(path, log))
|
||||||
|
{
|
||||||
|
// File open failed; skip write operation but continue with other processing
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (ShouldRotate(path, line.Length))
|
if (ShouldRotate(path, line.Length))
|
||||||
{
|
{
|
||||||
string archived = RotateFile(path);
|
string archived = RotateFile(path);
|
||||||
EnsureFileOpen(path, log);
|
if (!EnsureFileOpen(path, log))
|
||||||
|
{
|
||||||
|
// File reopen after rotation failed; skip this write
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OpenFile of;
|
||||||
|
if (_openFiles.TryGetValue(path, out of))
|
||||||
|
{
|
||||||
|
of.Writer.WriteLine(line);
|
||||||
|
of.Size += line.Length + Environment.NewLine.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (archived != null)
|
if (archived != null)
|
||||||
{
|
{
|
||||||
Action<string> onRotate = _onRotate;
|
Action<string> onRotate = _onRotate;
|
||||||
@@ -1031,13 +1026,21 @@ namespace EonaCat.LogStack.Flows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
OpenFile of;
|
OpenFile of;
|
||||||
if (_openFiles.TryGetValue(path, out of))
|
if (_openFiles.TryGetValue(path, out of))
|
||||||
{
|
{
|
||||||
of.Writer.WriteLine(line);
|
of.Writer.WriteLine(line);
|
||||||
of.Size += line.Length + Environment.NewLine.Length;
|
of.Size += line.Length + Environment.NewLine.Length;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// File handle disappeared between EnsureFileOpen and write attempt
|
||||||
|
WriteToConsoleError("[FileFlow] File handle for '" + path + "' missing after EnsureFileOpen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -1522,7 +1525,7 @@ namespace EonaCat.LogStack.Flows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureFileOpen(string path, LogEvent logEvent)
|
private bool EnsureFileOpen(string path, LogEvent logEvent)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1533,7 +1536,7 @@ namespace EonaCat.LogStack.Flows
|
|||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -1584,6 +1587,7 @@ namespace EonaCat.LogStack.Flows
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
of.Writer.Write(CsvHeader);
|
of.Writer.Write(CsvHeader);
|
||||||
|
of.Writer.Flush();
|
||||||
of.HasCsvHeader = true;
|
of.HasCsvHeader = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1600,6 +1604,7 @@ namespace EonaCat.LogStack.Flows
|
|||||||
{
|
{
|
||||||
of.Writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
of.Writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||||
of.Writer.WriteLine("<logs>");
|
of.Writer.WriteLine("<logs>");
|
||||||
|
of.Writer.Flush();
|
||||||
of.HasXmlHeader = true;
|
of.HasXmlHeader = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1614,6 +1619,8 @@ namespace EonaCat.LogStack.Flows
|
|||||||
WriteToConsoleError("[FileFlow] XML header write error for '" + path + "': " + ex.Message);
|
WriteToConsoleError("[FileFlow] XML header write error for '" + path + "': " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -1640,6 +1647,7 @@ namespace EonaCat.LogStack.Flows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
WriteToConsoleError("[FileFlow] Failed to open '" + path + "': " + ex.Message + diagnosis);
|
WriteToConsoleError("[FileFlow] Failed to open '" + path + "': " + ex.Message + diagnosis);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1650,6 +1658,7 @@ namespace EonaCat.LogStack.Flows
|
|||||||
WriteToConsoleError("[FileFlow] EnsureFileOpen unhandled error: " + ex.Message);
|
WriteToConsoleError("[FileFlow] EnsureFileOpen unhandled error: " + ex.Message);
|
||||||
}
|
}
|
||||||
catch { /* ignore */ }
|
catch { /* ignore */ }
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1870,6 +1879,7 @@ namespace EonaCat.LogStack.Flows
|
|||||||
|
|
||||||
private void PeriodicFlushLoop()
|
private void PeriodicFlushLoop()
|
||||||
{
|
{
|
||||||
|
WriteToConsoleError("[FileFlow] Periodic flush loop started");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!_isDisposing && !_cts.Token.IsCancellationRequested)
|
while (!_isDisposing && !_cts.Token.IsCancellationRequested)
|
||||||
@@ -2032,6 +2042,141 @@ namespace EonaCat.LogStack.Flows
|
|||||||
string.Concat(prefix, "_", CachedMachineName, "_", date.ToString(_dateFormat), _fileExtension));
|
string.Concat(prefix, "_", CachedMachineName, "_", date.ToString(_dateFormat), _fileExtension));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the logging directory with a cascading fallback strategy:
|
||||||
|
/// 1. Try the requested directory
|
||||||
|
/// 2. Try to fix permissions on the requested directory
|
||||||
|
/// 3. Fall back to %appdata%/EonaCat.LogStack/
|
||||||
|
/// 4. Fall back to TEMP directory
|
||||||
|
/// </summary>
|
||||||
|
private string ResolveLoggingDirectory(string requestedDirectory)
|
||||||
|
{
|
||||||
|
// Resolve relative paths first
|
||||||
|
string workingDirectory = requestedDirectory;
|
||||||
|
if (workingDirectory.StartsWith("./", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
workingDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, workingDirectory.Substring(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt 1: Use the requested directory
|
||||||
|
WriteToConsoleError($"[FileFlow] Attempting to use log directory: {workingDirectory}");
|
||||||
|
if (Helpers.DirectoryPermissionHelper.EnsureDirectoryHierarchy(workingDirectory))
|
||||||
|
{
|
||||||
|
WriteToConsoleError($"[FileFlow] Successfully using log directory: {workingDirectory}");
|
||||||
|
return workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteToConsoleError($"[FileFlow] Failed to use log directory: {workingDirectory}. Diagnosis: {Helpers.DirectoryPermissionHelper.GetAccessIssueDiagnosis(workingDirectory)}");
|
||||||
|
|
||||||
|
// Attempt 2: Try to fix permissions on the requested directory
|
||||||
|
WriteToConsoleError($"[FileFlow] Attempting to fix permissions on: {workingDirectory}");
|
||||||
|
if (TryFixDirectoryPermissions(workingDirectory) && Helpers.DirectoryPermissionHelper.EnsureDirectoryHierarchy(workingDirectory))
|
||||||
|
{
|
||||||
|
WriteToConsoleError($"[FileFlow] Successfully fixed permissions and using log directory: {workingDirectory}");
|
||||||
|
return workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteToConsoleError($"[FileFlow] Failed to fix permissions on: {workingDirectory}");
|
||||||
|
|
||||||
|
// Attempt 3: Fall back to AppData directory
|
||||||
|
string appDataDirectory = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"EonaCat.LogStack");
|
||||||
|
|
||||||
|
WriteToConsoleError($"[FileFlow] Attempting to use AppData log directory: {appDataDirectory}");
|
||||||
|
if (Helpers.DirectoryPermissionHelper.EnsureDirectoryHierarchy(appDataDirectory))
|
||||||
|
{
|
||||||
|
WriteToConsoleError($"[FileFlow] Successfully using AppData log directory: {appDataDirectory}");
|
||||||
|
OnDirectoryException?.Invoke(this, $"FileFlow: Could not use requested directory '{requestedDirectory}', fell back to '{appDataDirectory}'");
|
||||||
|
return appDataDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteToConsoleError($"[FileFlow] Failed to use AppData log directory: {appDataDirectory}. Diagnosis: {Helpers.DirectoryPermissionHelper.GetAccessIssueDiagnosis(appDataDirectory)}");
|
||||||
|
|
||||||
|
// Attempt 4: Fall back to TEMP directory
|
||||||
|
string tempDirectory = Path.GetTempPath();
|
||||||
|
WriteToConsoleError($"[FileFlow] Attempting to use TEMP log directory: {tempDirectory}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Helpers.DirectoryPermissionHelper.CanWrite(tempDirectory))
|
||||||
|
{
|
||||||
|
WriteToConsoleError($"[FileFlow] Successfully using TEMP log directory: {tempDirectory}");
|
||||||
|
OnDirectoryException?.Invoke(this, $"FileFlow: Could not use requested directory '{requestedDirectory}' or AppData, fell back to TEMP directory '{tempDirectory}'");
|
||||||
|
return tempDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Temp directory check failed
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteToConsoleError($"[FileFlow] Failed to use TEMP log directory: {tempDirectory}. This is a critical error - logging may not work.");
|
||||||
|
|
||||||
|
// Final fallback: return the temp path anyway, even if it might not work
|
||||||
|
OnDirectoryException?.Invoke(this, $"FileFlow: Critical - could not find any writable directory. Attempting to use TEMP: '{tempDirectory}'");
|
||||||
|
return tempDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to fix directory permissions across all platforms (Windows, Linux, Mac).
|
||||||
|
/// On Windows: Clears read-only attributes.
|
||||||
|
/// On Linux/Mac: Uses chmod to set full permissions.
|
||||||
|
/// Returns true if fix was successful or attempted, false if it failed or is not applicable.
|
||||||
|
/// </summary>
|
||||||
|
private bool TryFixDirectoryPermissions(string dirPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(dirPath))
|
||||||
|
{
|
||||||
|
// Try to create it first
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dirPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to set everyone permissions (handles all platforms)
|
||||||
|
if (Helpers.DirectoryPermissionHelper.TrySetEveryonePermissions(dirPath))
|
||||||
|
{
|
||||||
|
WriteToConsoleError($"[FileFlow] Set permissions on: {dirPath}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: On Windows, try clearing the read-only attribute directly
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dirInfo = new DirectoryInfo(dirPath);
|
||||||
|
if ((dirInfo.Attributes & FileAttributes.ReadOnly) != 0)
|
||||||
|
{
|
||||||
|
dirInfo.Attributes &= ~FileAttributes.ReadOnly;
|
||||||
|
WriteToConsoleError($"[FileFlow] Cleared read-only attribute on: {dirPath}");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WriteToConsoleError($"[FileFlow] Failed to fix permissions: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Unix-like systems, we've already attempted chmod via TrySetEveryonePermissions
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SetFileExtension(FileOutputFormat fmt)
|
private void SetFileExtension(FileOutputFormat fmt)
|
||||||
{
|
{
|
||||||
switch (fmt)
|
switch (fmt)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ namespace EonaCat.LogStack.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to create a temporary file to verify write access
|
// Try to create a temporary file to verify write access
|
||||||
var testFile = Path.Combine(dirPath, ".permission_test_" + Guid.NewGuid().ToString("."));
|
var testFile = Path.Combine(dirPath, ".permission_test_" + Guid.NewGuid().ToString("N"));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var fs = File.Create(testFile, 1, FileOptions.DeleteOnClose))
|
using (var fs = File.Create(testFile, 1, FileOptions.DeleteOnClose))
|
||||||
@@ -150,6 +151,115 @@ namespace EonaCat.LogStack.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to set full permissions for everyone on the directory.
|
||||||
|
/// Falls back to clearing the read-only attribute if needed on Windows.
|
||||||
|
/// Uses chmod on Linux/Mac and Windows permission APIs on Windows.
|
||||||
|
/// This is a best-effort operation for all platforms.
|
||||||
|
/// </summary>
|
||||||
|
public static bool TrySetEveryonePermissions(string dirPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dirPath) || !Directory.Exists(dirPath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
return TrySetWindowsPermissions(dirPath);
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
return TrySetUnixPermissions(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown platform - try whatever we can
|
||||||
|
return CanWrite(dirPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to set Windows directory permissions by clearing the read-only attribute.
|
||||||
|
/// </summary>
|
||||||
|
private static bool TrySetWindowsPermissions(string dirPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dirInfo = new DirectoryInfo(dirPath);
|
||||||
|
|
||||||
|
// Clear read-only attribute if set
|
||||||
|
if ((dirInfo.Attributes & FileAttributes.ReadOnly) != 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dirInfo.Attributes &= ~FileAttributes.ReadOnly;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Continue even if attribute clearing fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the directory is now writable
|
||||||
|
return CanWrite(dirPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to set Unix (Linux/Mac) directory permissions using chmod.
|
||||||
|
/// Sets full permissions (777) for the directory.
|
||||||
|
/// </summary>
|
||||||
|
private static bool TrySetUnixPermissions(string dirPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dirPath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use chmod to set full permissions (777)
|
||||||
|
var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "chmod",
|
||||||
|
Arguments = $"777 \"{dirPath}\"",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
process.WaitForExit(5000); // Wait up to 5 seconds
|
||||||
|
|
||||||
|
if (process.ExitCode == 0)
|
||||||
|
{
|
||||||
|
// Verify the directory is now writable
|
||||||
|
return CanWrite(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// chmod not available or failed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to grant write permissions on Windows by clearing the read-only attribute.
|
/// Attempts to grant write permissions on Windows by clearing the read-only attribute.
|
||||||
/// This is a best-effort operation and may fail on restricted systems.
|
/// This is a best-effort operation and may fail on restricted systems.
|
||||||
|
|||||||
@@ -51,21 +51,24 @@ namespace EonaCat.LogStack.Test.Web
|
|||||||
var logBuilder = new LogBuilder();
|
var logBuilder = new LogBuilder();
|
||||||
logBuilder.WithTimestampMode(TimestampMode.Local);
|
logBuilder.WithTimestampMode(TimestampMode.Local);
|
||||||
logBuilder.WriteToConsole();
|
logBuilder.WriteToConsole();
|
||||||
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Csv);
|
|
||||||
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.StructuredJson);
|
// Use absolute path to ensure directory creation works
|
||||||
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Text);
|
var logDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "EonaCat.LogStack", "Logs");
|
||||||
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Json);
|
Directory.CreateDirectory(logDirectory);
|
||||||
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Xml);
|
|
||||||
|
logBuilder.WriteToFile(logDirectory, outputFormat: FileOutputFormat.Csv);
|
||||||
|
logBuilder.WriteToFile(logDirectory, outputFormat: FileOutputFormat.StructuredJson);
|
||||||
|
logBuilder.WriteToFile(logDirectory, outputFormat: FileOutputFormat.Text);
|
||||||
|
logBuilder.WriteToFile(logDirectory, outputFormat: FileOutputFormat.Json);
|
||||||
|
logBuilder.WriteToFile(logDirectory, outputFormat: FileOutputFormat.Xml);
|
||||||
logBuilder.WriteToTcp("127.0.0.1", 514);
|
logBuilder.WriteToTcp("127.0.0.1", 514);
|
||||||
//logBuilder.WriteToEncryptedFile("./logs");
|
//logBuilder.WriteToEncryptedFile("./logs");
|
||||||
//logBuilder.WriteDiagnostics();
|
//logBuilder.WriteDiagnostics();
|
||||||
logBuilder.WriteToAudit(
|
logBuilder.WriteToAudit(
|
||||||
directory: "./audit",
|
directory: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "EonaCat.LogStack", "Audit"),
|
||||||
auditLevel: AuditLevel.WarningAndAbove, // only Warn+ go to audit trail
|
auditLevel: AuditLevel.WarningAndAbove, // only Warn+ go to audit trail
|
||||||
includeProperties: true);
|
includeProperties: true);
|
||||||
//logBuilder.WriteToRollingBuffer();
|
//logBuilder.WriteToRollingBuffer();
|
||||||
var consoleFlow = logBuilder.GetFlow(typeof(ConsoleFlow));
|
|
||||||
logBuilder.WriteToThrottled(consoleFlow);
|
|
||||||
logBuilder.WriteToStatusFlow(new List<ServiceMonitoring.ServiceStatus> { new ServiceMonitoring.ServiceStatus { Host = "google.com", Port = 443, ServiceType = ServiceMonitoring.ServiceType.HTTPS } }, checkInterval: TimeSpan.FromSeconds(5));
|
logBuilder.WriteToStatusFlow(new List<ServiceMonitoring.ServiceStatus> { new ServiceMonitoring.ServiceStatus { Host = "google.com", Port = 443, ServiceType = ServiceMonitoring.ServiceType.HTTPS } }, checkInterval: TimeSpan.FromSeconds(5));
|
||||||
//logBuilder.WriteToSignalR();
|
//logBuilder.WriteToSignalR();
|
||||||
//logBuilder.WriteToEmailFlow();
|
//logBuilder.WriteToEmailFlow();
|
||||||
|
|||||||
Reference in New Issue
Block a user