线程安全文件写入程序锁定释放问题



我正在运行一个.Net Web API。我有下面的代码来创建一个日志编写器函数,它是线程安全的,可以在Parallel.ForEach()函数中使用,而不会因竞争条件而导致任何问题。

我面临的问题是,限制日志文件的大小,并通过备份旧的日志文件来创建新的日志文件。当文件大小达到代码中定义的最大大小时,应该将现有文件复制到Backup_currentfilename.log,并将新日志文件重新创建为新日志文件。然而,当它达到最大大小时,它成功地创建了备份文件,但当它试图删除旧文件时,它被静态构造函数方法使用:

private static readonly string ApplicationLogFilePath = ConfigurationManager.AppSettings.Get("ApplicationLogFilePath");
private static readonly string ApplicationLogFileName = ConfigurationManager.AppSettings.Get("ApplicationLogFileName");
private static readonly long LogFile_MaxSizeInBytes = 5242880;
private static readonly string Updated_ApplicationLogFilePath = ApplicationLogFilePath.EndsWith("\") ? ApplicationLogFilePath : $"{ApplicationLogFilePath}\";
public class ThreadSafeLogger
{
private static readonly string TSLogFileName = ConfigurationManager.AppSettings.Get("TSLogFileName");
private static readonly string TSLogFilePath = $"{Updated_ApplicationLogFilePath}{TSLogFileName}";
static readonly TextWriter tw;
static ThreadSafeLogger()
{
try
{
try
{
FileInfo logFileInfo = new FileInfo(TSLogFilePath);
if (logFileInfo.FullName.EndsWith("\") || string.IsNullOrEmpty(logFileInfo.Name) || !Regex.IsMatch(TSLogFilePath, @"[A-z]:[\](?:w+[\])+(w+.(?:([tT][xX][tT]|[lL][oO][gG])))"))
{
// Give a correct file name
TSLogFilePath = $"{Updated_ApplicationLogFilePath}TSLog.log";
}
}
catch (Exception)
{
// Give a correct file name
TSLogFilePath = $"{Updated_ApplicationLogFilePath}TSLog.log";
}
// Create a backup of existing log file if size is more than xxxx bytes
try
{
if (tw != null)
{
tw.Close();
}
try
{
FileInfo fileInfo = new FileInfo(TSLogFilePath);
// Move the file to circular log
if (fileInfo.Length > LogFile_MaxSizeInBytes)
{
File.Copy(TSLogFilePath, $"{Updated_ApplicationLogFilePath}BACKUP_{TSLogFileName}", true);
File.Delete(TSLogFilePath);
}
// Create new empty log file
if (!File.Exists(TSLogFilePath))
{
Directory.CreateDirectory(Updated_ApplicationLogFilePath);
File.CreateText(TSLogFilePath);
}
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message + " > GlobalHelper > ThreadSafeLogger()");
}
}
catch (Exception ex)
{
// Ignore errors
Trace.WriteLine(ex.Message + " > GlobalHelper > ThreadSafeLogger()");
}
tw = TextWriter.Synchronized(File.AppendText(TSLogFilePath));
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message + " > GlobalHelper > ThreadSafeLogger()");
}
}
~ThreadSafeLogger()
{
try
{
tw.Close();
}
catch (Exception ex)
{
Trace.WriteLine($"Error in ~ThreadSafeLogger(): {ex.Message}");
}
}
public static void WriteTSLog(string VerboseText, [CallerLineNumber] int LineNumber = 0, [CallerMemberName] string SourceFunction = null, [CallerFilePath] string FilePath = null)
{
DateTime TimeStamp = DateTime.Now;
try
{
// Create a backup of existing log file if size is more than xxxx bytes
try
{
try
{
FileInfo fileInfo = new FileInfo(TSLogFilePath);
// Move the file to circular log
if (fileInfo.Length > LogFile_MaxSizeInBytes)
{
File.Copy(TSLogFilePath, $"{Updated_ApplicationLogFilePath}BACKUP_{TSLogFileName}", true);
File.Delete(TSLogFilePath);
}
// Create new empty log file
if (!File.Exists(TSLogFilePath))
{
Directory.CreateDirectory(Updated_ApplicationLogFilePath);
File.CreateText(TSLogFilePath);
}
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message + " > GlobalHelper > WriteTSLog()");
}
}
catch (Exception ex)
{
// Ignore errors
Trace.WriteLine(ex.Message + " > GlobalHelper > WriteTSLog()");
}

// Extract filename from file path
string FileName = "";
try
{
FileInfo fileInfo = new FileInfo(FilePath);
FileName = fileInfo.Name;
}
catch (Exception)
{
FileName = FilePath;
}
TSLog($"[{TimeStamp:dd-MMM-yyyy HH:mm:ss.fff}][{LineNumber}][{FileName} > {SourceFunction}()]: {VerboseText}", tw);
}
catch (Exception ex)
{
try
{
using (EventLog eventLog = new EventLog("Application"))
{
eventLog.Source = "Application";
eventLog.WriteEntry($"ThreadSafeLogger: failed to write to log file '{TSLogFilePath}'.{Environment.NewLine}Message:'[{TimeStamp:dd-MMM-yyyy HH:mm:ss.fff}][{LineNumber}][{FilePath} > {SourceFunction}()]: {VerboseText}'.{Environment.NewLine}Reason: {ex.Message}", EventLogEntryType.Error, 1111);
}
}
catch
{
Trace.WriteLine(ErrorMessagePrefix + " > GlobalHelper > WriteTSLog");
}
}
}
private static readonly object _syncObject = new object();
private static void TSLog(string LogMessage, TextWriter w)
{
try
{
lock (_syncObject)
{
w.WriteLine(LogMessage);
w.Flush();
}
}
catch (Exception ex)
{
Trace.WriteLine($"Error in TSLog(): {ex.Message}");
}
}
}

当前日志文件在静态方法中始终保持打开状态,除非重新启动应用程序,否则无法执行循环日志记录。

在上面的代码中,我尝试添加一个从未被调用的析构函数方法(原因很明显(。我该如何解决这个问题?

我会缓冲日志写入,而不是直接写入磁盘。I.e创建一个可以从任何线程写入的concurrentQueue,并创建一个从队列中读取并写入磁盘的线程。这个单独的线程可以安全地移动文件,因为它是唯一可以访问该文件的线程。这也应该更快,因为每个想要登录的线程不必竞争单个锁。

我也不会使用静态方法来进行日志记录,而是创建一个执行所有日志记录的类,如果您想要全局访问,请创建该类的单例实例。

例如

private BlockingCollection<string> logQueue = new();
private Task logTask;
public void StartLogThread()
{
logTask = Task.Factory.StartNew(WriteThreadMain, TaskCreationOptions.LongRunning);
}
public void WriteToLog(string message) => logQueue.Add(message);
private void WriteThreadMain()
{
// Create file
foreach (var message in logQueue.GetConsumingEnumerable())
{
// Write message
}
// Close file
}
public Task CloseLogThread()
{
logQueue.CompleteAdding();
return logTask;
}

或者只使用一个为您完成所有这些的日志库,它可能会更快、更可靠、更灵活、更好。

相关内容

  • 没有找到相关文章

最新更新