【问题标题】:C# Singleton Logging ClassC# 单例日志记录类
【发布时间】:2010-07-13 05:15:15
【问题描述】:

我正在尝试找出登录我正在使用的异步 IO Web 服务器的最佳策略。我认为最简单的方法是创建一个单例类,让 Filestreams 为相应的日志文件保持打开状态,这样我就可以执行以下操作:

Util.Logger.GetInstance().LogAccess(str);

或者类似的东西。

我的班级是这样的:

public sealed class Logger {
   private static StreamWriter sw;
   private static readonly Logger instance = new Logger();
   private Logger() {
      sw = new StreamWriter(logfile);
   }
   public static Logger GetInstance() {
      return instance;
   }
   public void LogAccess(string str) {
      sw.WriteLine(str);
   }
}

这一切都只是在我的脑海中,我正在寻找有关如何使其变得更好并确保我做得正确的建议。最重要的是我需要它是线程安全的,显然它不在当前状态。不确定最好的方法。

【问题讨论】:

  • 您使用的是 .NET 4 吗?新的异步库可以帮助您。 (我也将借此机会指出这在 F# 中将如何优雅地完成,并请假!)
  • 使用 Singleton 与仅使用静态方法相比有何价值?
  • 我喜欢你想自己尝试的事实。构建日志并不难,但创建一种处理线程的好方法是一项挑战。建议的框架都可以,但是自己创建它会给你一个很好的机会来学习和使用线程安全。如果您希望多个应用程序(或在 ASP.NET 应用程序池/服务器中)写入同一个文件,这确实具有挑战性,因为在这种情况下,仅线程安全是不够的。
  • 我个人喜欢有一个日志类的实例,无论是单例的、静态的,还是不是,因为我可以在每个实例级别自定义日志记录的行为。我可以为我的应用程序的一部分提供一个实例,为我的应用程序的另一部分提供一个不同的实例,以便我可以分别自定义两者的日志记录行为。

标签: c# logging singleton thread-safety


【解决方案1】:

如果您使用 NLog,这将自动为您处理 - 您在 .config 文件中定义所有记录器,然后通过静态 LogManager 类(即单例)访问所有记录器。

这是一个说明 NLog 的线程安全特性的示例:

https://github.com/nlog/nlog/wiki/Tutorial#Adding_NLog_to_an_application

【讨论】:

【解决方案2】:

有一个方法TextWriter.Synchronized 可以生成TextWriter 的线程安全版本。试试看。

【讨论】:

    【解决方案3】:

    a) 不要在方法名称中包含“Log”。很明显,记录器记录。 .Warning、.Error 等是更好的方法名称,因为它们描述了日志条目的级别。

    b) 创建一个写入日志的后台线程。

    c) 将记录方法中的条目排入队列并向工作线程发出信号。

    d) 使用(不知道方法名记错了没有)

    var methodInfo = new StackFrame(1).GetMethod();
    var classAndMethod = methodInfo.DeclaringType.Name + "." + methodInfo.Name;
    

    获取调用方法。

    这样做只会给你一个访问文件的线程。

    【讨论】:

    • 我不同意(a),因为WarningError 不是动词。按照惯例,方法名称应该是动词。 Warn 会更好,但是我们应该如何命名记录错误和严重错误的方法? LogWarningLogError 在 IMO 上要好得多,或者我宁愿使用一个名为 Log 的通用方法,并带有一个显示类型的 severity 参数。虽然 (b) 可以提高性能,但大多数日志框架不会在后台登录。同步执行很棘手,通常更安全。
    • 还有一条规则说类名不能在方法名中重复。在这种情况下,一切都与品味有关,因为很明显 Error 和 LogError 做同样的事情。我更喜欢 Error 方法而不是带有严重性参数的 Log 方法,因为它的输入和阅读更少。但正如我所说:一切都与品味有关。 (b) 我看不出在单独的线程中写入日志是多么棘手或不安全。这很简单。一个线程,一个队列,一个锁,什么都不需要了
    • 我没有说它很难实现;问题是日志记录通常是业务流程的重要组成部分,在这种情况下,您不想在知道日志消息被保留之前继续处理。当然,在其他情况下,保留日志消息队列可能会很好。这取决于。但出于这个原因,日志框架通常会同步处理它们。
    • 当以两种方式引发致命异常时,应用程序可以关闭:A)您将其关闭,然后在发出信号后等待日志线程退出(让它写入所有条目)。 b) 未处理的异常终止您的应用程序。如果线程在它们的工作中间被终止,那么你就明白了。我认为他们是。如果捕获未处理的异常以将其写入某些内容还不够,那么异步日志记录不适合您。
    • 有人能知道在 AppDomain.CurrentDomain.UnhandledException 事件引发之前是否所有线程都停止了吗?
    【解决方案4】:

    也许你应该试试 NLog 或 Log4net。它们都是很棒的日志框架。

    但是如果你确实想编写自己的日志组件,那么在输出日志消息时,Lock 是必须的。 在内存中缓冲日志消息并一次将它们写入文件是很常见的。

    【讨论】:

      【解决方案5】:

      为您解决这些问题的另一个框架是the Object Guy's logging framework。它可以选择在后台登录。多个线程可以登录到同一个文件。并且多个进程可以登录到同一个文件。

      【讨论】:

        【解决方案6】:

        如果您想从多个线程写入同一个文件,我认为没有任何简单的方法可以解决锁定问题。

        因此,简单的解决方案是在对StreamWriter 的任何调用周围添加lock。或者,您可以将输出缓冲在内存中,并且偶尔将其写入文件,这仍然需要锁定,但锁定争用会低得多。但是,如果您达到这个长度,您不妨使用像 log4net, which is thread-safe 这样的适当的日志记录框架。

        【讨论】:

          【解决方案7】:

          或者你可以使用一个只有共享方法的类..

          Imports System.Threading
          
          Public Class Logger
              Private Shared ReadOnly syncroot As New Object
          
              Public Shared Sub log(ByVal vInt As Integer)
                  ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf logThread), CStr(vInt))
              End Sub
          
              Public Shared Sub log(ByVal vStr As String)
                  ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf logThread), vStr)
              End Sub
          
              Private Shared Sub logThread(ByVal o As Object)
                  Dim str As String = CStr(o)
                  SyncLock syncroot
                      Using objWriter As New System.IO.StreamWriter(GetLogPath, True)
          
                          objWriter.WriteLine(str)
                          objWriter.Close()
          
                      End Using
                  End SyncLock
              End Sub
          
              Private Shared Function GetLogPath() As String
                  Return "logs.txt"
              End Function
          End Class
          

          我发现这种方式比使用单例更有用:

          Logger.log("Something to log")
          

          干杯

          【讨论】:

            猜你喜欢
            • 2016-04-22
            • 1970-01-01
            • 2015-12-21
            • 2011-01-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-09-04
            • 1970-01-01
            相关资源
            最近更新 更多