线程安全日志记录类实现

Sea*_*man 50 c# logging multithreading thread-safety

以下是实现相当简单的线程安全日志记录类的正确方法吗?

我知道我从未明确关闭过TextWriter,这会是一个问题吗?

当我最初使用该TextWriter.Synchronized方法时,它似乎没有工作,直到我在静态构造函数中初始化它并使它只读它:

public static class Logger
{
    static readonly TextWriter tw; 

    static Logger()
    {
        tw = TextWriter.Synchronized(File.AppendText(SPath() + "\\Log.txt")); 
    }

    public static string SPath()
    {
        return ConfigManager.GetAppSetting("logPath"); 
    }

    public static void Write(string logMessage)
    {
        try
        {
            Log(logMessage, tw);
        }
        catch (IOException e)
        {
            tw.Close();
        }
    }

    public static void Log(string logMessage, TextWriter w)
    {
        w.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(),
            DateTime.Now.ToLongDateString());
        w.WriteLine("  :");
        w.WriteLine("  :{0}", logMessage);
        w.WriteLine("-------------------------------");

        // Update the underlying file.
        w.Flush();
    }
}
Run Code Online (Sandbox Code Playgroud)

x0n*_*x0n 93

我将采取与其他答案完全不同的方法,并假设您实际上想要学习如何编写更好的线程感知代码,并且不寻找我们的第三方建议(即使您实际上可能最终使用一.)

正如其他人所说,你正在创建一个线程安全的TextWriter,这意味着对WriteLine的调用是线程安全的,这并不意味着一堆调用WriteLine将作为原子操作执行.我的意思是不能保证四个WriteLine调用将按顺序发生.你可能有一个线程安全的TextWriter,但你没有线程安全的Logger.Log方法;)为什么?因为在这四个调用期间的任何时候,另一个线程可能还决定调用Log.这意味着您的WriteLine通话将不同步.解决这个问题的方法是使用如下lock语句:

private static readonly object _syncObject = new object();

public static void Log(string logMessage, TextWriter w)    {
   // only one thread can own this lock, so other threads
   // entering this method will wait here until lock is
   // available.
   lock(_syncObject) {
      w.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(),
          DateTime.Now.ToLongDateString());
      w.WriteLine("  :");
      w.WriteLine("  :{0}", logMessage);
      w.WriteLine("-------------------------------");
      // Update the underlying file.
      w.Flush();
   }
}
Run Code Online (Sandbox Code Playgroud)

所以,现在你有一个线程安全的TextWriter和线程安全的Logger.

合理?

  • 如果你这样做,你也应该放弃`TextWriter.SyncTextWriter`.对`SyncTextWriter`的每个方法调用都会锁定实例.如果要在自己的Logger类方法中包装对`TextWriter`的调用,则不需要这样做.如果你传递一个原始的`SyncTextWriter`,你可以像这样死锁:`lock(textWriter){textWriter.WriteLine("test"); // oops deadlock` (7认同)
  • 嘿,如果您打算在性能关键的应用程序中使用它(线程通常与速度有关,是吗?)您不能让所有线程等待所有这些 WriteLine 和 Flush 调用完成!就我个人而言,我启动一个单独的日志线程(每个 AppDomain)在后台工作,并使用无锁的生产者-消费者集合,以便即使有许多活动线程,日志调用也非常快。 (2认同)

Ree*_*sey 5

虽然调用TextWriter.Synchronized将保护该单个实例TextWriter,但它不会同步您的写入,以便一个"Log"调用在文件内保持在一起.

如果您从多个线程调用Write(或Log使用内部TextWriter实例),则各个WriteLine调用可能会交织在一起,使您的日期和时间戳无法使用.

我个人会使用已存在的第三方日志记录解决方案.如果这不是一个选项,那么自己同步(即使使用简单的锁定)可能比使用框架的TextWriter.Synchronized包装器更有用.


Arg*_*a C 5

有人在今天讨论一些日志记录问题时向我指出了这篇文章。我们在这里已经有了很好的答案,但我添加我的答案只是为了展示一个更简单的Logger课程版本,它以完全相同的Threadsafe方式完成相同的事情。
这里要注意的一件主要事情是,TextWriter.Synchronized线程安全不需要,因为我们正在将文件写入适当的lock.

注意:这已经在 x0n 答案的评论部分中讨论过。

public static class Logger
{
    static readonly object _locker = new object();

    public static void Log(string logMessage)
    {
        try
        {
            var logFilePath = Path.Combine(@"C:\YourLogDirectoryHere", "Log.txt");
            //Use this for daily log files : "Log" + DateTime.Now.ToString("yyyy-MM-dd") + ".txt";
            WriteToLog(logMessage, logFilePath);
        }
        catch (Exception e)
        {
            //log log-exception somewhere else if required!
        }
    }

    static void WriteToLog(string logMessage, string logFilePath)
    {
        lock (_locker)
        {
            File.AppendAllText(logFilePath,
                    string.Format("Logged on: {1} at: {2}{0}Message: {3}{0}--------------------{0}", 
                    Environment.NewLine, DateTime.Now.ToLongDateString(),
                    DateTime.Now.ToLongTimeString(), logMessage));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

要记录一些东西,只需调用

Logger.Log("Some important event has occurred!");
Run Code Online (Sandbox Code Playgroud)

它会做一个这样的日志条目

登录:2015 年 10 月 7 日 at:02
:11 :23消息:发生了一些重要事件!
--------------------