Реализация класса безопасного ведения логов - PullRequest
50 голосов
/ 01 июня 2011

Будет ли следующий способ правильной реализации довольно простого поточно-ориентированного класса ведения журнала?

Я знаю, что никогда не закрывал явно 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();
    }
}

Ответы [ 5 ]

92 голосов
/ 01 июня 2011

Я собираюсь использовать здесь совершенно иной подход, чем другие ответы, и предположим, что вы действительно хотите научиться писать лучший код с поддержкой потоков, а не ищите от нас сторонних предложений (даже если вы на самом деле можетев конечном итоге используйте один.)

Как уже говорили другие, вы создаете потокобезопасный 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();
   }
}

Итак, теперь у вас есть потокобезопасный TextWriter И потокобезопасный Logger.

Имеет смысл?

5 голосов
/ 01 июня 2011

При вызове TextWriter.Synchronized будет защищать этот единственный экземпляр TextWriter, он не будет синхронизировать ваши записи, так что один вызов "Log" останется вместе внутри файла.

Если вы наберете Write (или Log с использованием внутреннего экземпляра TextWriter) из нескольких потоков, отдельные вызовы WriteLine могут быть переплетены, что сделает ваши метки даты и времени непригодными.

Я бы личноиспользуйте стороннее решение для ведения журнала, которое уже существует для этого.Если это не вариант, синхронизация этого самостоятельно (даже с простой блокировкой), вероятно, будет более полезной, чем использование оболочки TextWriter.Synchronized платформы.

2 голосов
/ 01 июня 2011

Вы должны заглянуть в этот класс (часть .NET 2.0), не нужно «создавать» свой собственный регистратор.позволяет вам войти в текстовый файл, просмотреть события и т. д.

http://msdn.microsoft.com/en-us/library/system.diagnostics.tracesource.aspx

Ваш метод «Журнал» может выглядеть примерно так (при условии, что существует внутренняя переменная-член, называемая 'traceSource)'):

    public void Log(TraceEventType eventType, string message)
    {
        this.traceSource.TraceEvent(eventType, 0, message);
        this.traceSource.Flush();
    }

Поддерживает этот раздел конфигурации, который называет TraceSource и имеет некоторые настройки Config.Предполагается, что когда вы создаете TraceSource в своем регистраторе, вы создаете его экземпляр с одним из источников трассировки, указанных в конфигурации.

<system.diagnostics>
<sources>
  <source name="Sample" switchValue="Information,ActivityTracing">
    <listeners>
      <add name="file"
         initializeData="C:\temp\Sample-trace.log"
         traceOutputOptions="DateTime"
         type="System.Diagnostics.TextWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
    </listeners>
  </source>
</sources>

Кроме того, не создавайте свой регистраторстатичный.Вместо этого используйте Enterprise Library 5.0 Unity для внедрения зависимостей / IOC.

Надеюсь, это поможет!

1 голос
/ 06 октября 2015

Кто-то указал мне на этот пост, обсуждая некоторые проблемы с журналированием сегодня. У нас уже есть довольно хорошие ответы здесь, но я добавляю свой ответ просто для того, чтобы показать более простую версию класса 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));
        }
    }
}

Чтобы что-то зарегистрировать, просто позвоните как

Logger.Log("Some important event has occurred!");

И в журнале появится такая запись

Зарегистрирован: 07 октября 2015 года в: 02: 11: 23
Сообщение: произошло важное событие!
--------------------

0 голосов
/ 01 июня 2011

Если вы ищете простой способ обработки кода, средство уже существует в .NET:

http://msdn.microsoft.com/en-us/library/system.diagnostics.trace.aspx

Кроме того, сторонние инструменты предоставят вам надежные решения для ведения журнала; примеры включают log4net , nLog и Enterprise Library .

Я действительно рекомендую не изобретать велосипед на этом:)

...