实现非阻塞线程安全单例日志记录 class

Implementing Non blocking thread safe singleton logging class

我想编写一个记录器 class,它根据用户操作将输出写入日志文件。为此,我写了一条日志消息class。以下是我的。

public class LogMessage
{
    private  static Object padlock = new Object();
    private static string serviceDirectory ="C:\MySite\";
    private static string filePath = "\Logs\Combined\ActivityMonitorLog-" + DateTime.Now.ToString("dd'-'MM'-'yyyy") + ".log";

    private static volatile LogMessage instance = new LogMessage();
    public static LogMessage Instance
    {
        get
        {
            if (instance == null)
            {
                lock (padlock)
                {
                    if (instance == null)
                        instance = new LogMessage();
                }
            }

            return instance;
        }
    }


    public void SaveLogMessage(string userName, string message, string stackTrace)
    {
        lock (padlock)
        {
            using (StreamWriter logWriter =
                new StreamWriter(serviceDirectory + filePath, true))
            {
                string logMessage = "";
                if (!string.IsNullOrEmpty(stackTrace))
                    logMessage = string.Format("{0} >> user : {1} ::: {2} ::: stack trace ::: {3}", DateTime.Now.ToString(), userName, message,stackTrace);
                else
                    logMessage = string.Format("{0} >> user : {1} ::: {2}", DateTime.Now.ToString(), userName, message);

                logWriter.WriteLine(logMessage);

            }
        }
    }
}

当我想做一个日志条目时,我正在调用下面的 about 方法

LogMessage.Instance.SaveLogMessage("My User", "Enter Logout PageLoad : Current Method :" + "Page_Load @ Logout.aspx.cs", "");

我相信我已经实现了单例部分和线程安全。但我不认为这是非阻塞的,因为多个用户同时登录,每个用户都必须等到文件可以写入。我正在考虑使用 backgroundworker 从 LogMessage 单例对象调用此 SaveLogMessage()。这是正确的做法吗?

更新

我已经使用 backgroundworker 实现了这个,完整的 class 答案中提到了如何调用。感谢输入 @bret 和 @huan

对于您的 "instance" 字段,请使用 "readonly" 而不是 "volatile",因为该字段的值永远不会改变。

您甚至不需要 public 实例 属性--您可以将 SaveLogMessage 设为静态方法并直接使用它。

在 SaveLogMessage 正文中,您可以使用 Task.Run(() => { ... }) 在后台线程上执行日志记录操作以使其成为非阻塞。如果您非常频繁地记录,这可能不是非常有效。

考虑改用 System.Diagnostics.Trace,这会给您带来更大的灵活性。

  1. 写时最好使用ReaderWriterLockslim 只加写锁
  2. 我认为使用 ReaderWriterLockslim 的静态方法就足够了。 有个不错的posthere
  3. 如果你想在这里使用单例,你可以使用Lazy参考Implementing the Singleton Pattern in C#

顺便说一句,为什么不使用 log4net 或任何其他现有库。

最后,我通过使用后台工作者成功地实现了非阻塞、线程安全、单例日志记录 class。我正在提交我的解决方案,以防有一天有人发现它有用。

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Threading;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Diagnostics;
using System.Web.Configuration;
/// <summary>
/// Summary description for LogMessage
/// </summary>
public class LogMessage
{
    static ReaderWriterLock locker = new ReaderWriterLock();

    private static string serviceDirectory = HttpContext.Current != null ?
        AppDomain.CurrentDomain.BaseDirectory :
        Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
    private static string fullpath = serviceDirectory + "\ActivityLog.log";
    private static readonly LogMessage instance = new LogMessage();

    public static LogMessage Instance
    {
        get { return instance; }
    }

    public void SaveLogMessage(string userName, string message, string stackTrace, bool inOut)
    {
        bool EnableActivityLogging = false;

        if (string.IsNullOrEmpty(WebConfigurationManager.AppSettings["EnableActivityLogging"]))
            return;

        EnableActivityLogging = Convert.ToBoolean(WebConfigurationManager.AppSettings["EnableActivityLogging"]);

        if (!EnableActivityLogging)
            return;

        BackgroundWorker logbw = new BackgroundWorker();
        logbw.DoWork += logbw_DoWork;
        logbw.RunWorkerCompleted += logbw_RunWorkerCompleted;

        List<string> paramList = new List<string>();
        paramList.Add(userName);
        paramList.Add(message);
        paramList.Add(stackTrace);
        paramList.Add(inOut.ToString());

        if (!logbw.IsBusy)
        {
            logbw.RunWorkerAsync(paramList);
        }
    }

    void logbw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Debug.Write("Log Message Background Worker is now free...");
    }

    void logbw_DoWork(object sender, DoWorkEventArgs e)
    {
        List<string> paramList = (List<string>)e.Argument;
        string userName = paramList[0].ToString();
        string message = paramList[1].ToString();
        string stackTrace = paramList[2].ToString();
        bool inOut = bool.Parse(paramList[3].ToString());

        try
        {
            locker.AcquireWriterLock(int.MaxValue);
            using (StreamWriter logWriter =
                new StreamWriter(fullpath, true))
            {
                string logMessage = "";
                if (!string.IsNullOrEmpty(stackTrace))
                {
                    if (inOut)//IN
                    {
                        logMessage = string.Format("{0} U:{1} IN:{2} E:{3}", DateTime.Now.ToString(), userName, message, stackTrace);
                    }
                    else//OUT
                    {
                        logMessage = string.Format("{0} U:{1} OUT:{2} E:{3}", DateTime.Now.ToString(), userName, message, stackTrace);
                    }
                }
                else
                {
                    if (inOut)//IN
                    {
                        logMessage = string.Format("{0} U:{1} IN:{2}", DateTime.Now.ToString(), userName, message);
                    }
                    else//OUT
                    {
                        logMessage = string.Format("{0} U:{1} OUT:{2}", DateTime.Now.ToString(), userName, message);
                    }
                }

                logWriter.WriteLine(logMessage);

            }
        }
        finally
        {
            locker.ReleaseWriterLock();
        }
    }

}

现在我可以像下面这样使用这个单例对象来保存日志条目

    LogMessage.Instance.SaveLogMessage(Context.User.Identity.Name, "Custom Message" + " M:" + MethodInfo.GetCurrentMethod().Name + "@" + Path.GetFileName(Page.AppRelativeVirtualPath), "", true);