Laden...

LogFileWriter mit Multithreaded Unterstützung als Singelton implementiert

Erstellt von t2t vor 14 Jahren Letzter Beitrag vor 14 Jahren 6.722 Views
T
t2t Themenstarter:in
415 Beiträge seit 2007
vor 14 Jahren
LogFileWriter mit Multithreaded Unterstützung als Singelton implementiert

Beschreibung:

Ich habe hier eine kleine Klasse, mit der sich ein einfaches Logging in der eigenen Applikation implementieren lässt. Gerade in Projekten die Multithreading verwenden, wird das Debuggen sehr schwer und es bietet sich an, gewisse Aktionen im Programmablauf einfach in ein Logfile zu schreiben, oder auf diese Weise Exceptions mitzuloggen.

Die Klasse ist nach dem Singelton Entwurfsmuster implementiert.


namespace LogFileWriter
{
    /// <summary>
    /// Klasse zum schreiben eines LogFiles, mit Multithreaded Unterstützung.
    /// </summary>
    public class LogWriter
    {
        private static LogWriter instance;
        private static object syncLock = new object();

        private string _logName = Environment.CurrentDirectory + @"\log\logfile.txt";

        public static LogWriter GetInstance()
        {
            // Multithreaded Support 
            if (instance == null)
            {
                lock (syncLock)
                {
                    if (instance == null)
                    {
                        instance = new LogWriter();
                    }
                }
            }

            return instance;
        }

        private LogWriter()
        {
        }

        /// <summary>
        /// Loggt eine Exception
        /// </summary>
        /// <param name="exception"></param>
        public void LogException(Exception exception)
        {
            string msg = exception.Message + " (" + exception.Source + ")";
            LogMessage(msg, "Fehler");
        }

        /// <summary>
        /// Schreibt eine Nachricht ins Logfile
        /// </summary>
        /// <param name="message">Die zu loggende Nachricht</param>
        /// <param name="severity">Nachrichtenkategorie (z.B. Info, Fehler, etc.)</param>
        public void LogMessage(string message, string severity)
        {
            string body = DateTime.Now.ToString("HH:mm:ss")
                + " - "
                + severity
                + ":"
                + Environment.NewLine + message;

            if (!Directory.Exists(Path.GetDirectoryName(_logName)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(_logName));
            }

            if (!File.Exists(_logName))
            {
                body = "Logfile erstellt am "
                    + DateTime.Now.ToShortDateString()
                    + " um "
                    + DateTime.Now.ToShortTimeString()
                    + Environment.NewLine
                    + Environment.NewLine
                    + body;
            }

            try
            {
                using (StreamWriter sw = new StreamWriter(_logName, true))
                {
                    sw.WriteLine(body);
                }
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }
}

Schlagwörter: File Logging, Logfile, Singleton, Multithreading, LogWriter, überwachung

6.911 Beiträge seit 2009
vor 14 Jahren

Multithreaded Unterstützung

Diese Unterstützung ist nur für Instanz des Singleton implementiert. Der Rest ist "thread-unsicher".

D.h. es gibt keine Sperre für die Instanzmethoden - da vom Singleton nur eine Instanz existiert greifen alle Aufrufe auf dieselbe Instanz zu.

Folgender einfacher Code schlägt zB fehl:


using System;
using System.Threading;

namespace ConsoleApplication1
{
	class Program
	{
		static void Main(string[] args)
		{
			ManualResetEvent signal = new ManualResetEvent(false);

			for (int i = 0; i < 2; i++)
			{
				ThreadPool.QueueUserWorkItem((o) =>
					{
						signal.WaitOne();
						LogFileWriter.LogWriter.GetInstance().LogMessage(
							string.Format("Testeintrag {0}", (int)o),
							"Info");
					}, i);
			}

			signal.Set();

			Console.ReadKey();
		}
	}
}

Desweiteren ist Environment.CurrentDiretory oft nicht das gewünschte Verzeichnis. ZB wenn die Anwendung von einem Link gestartet wird dann ist das CurrentDirectory nicht das Application-Directory (das vermutlich gewünscht ist). Um das "korrekte" Verzeichnis zu ermitteln gibt es die Möglichkeiten:*bei WinForms (bzw. bei einem Verweis auf System.Windows.Forms): Application.StartupPath *Path.GetDirectory(Assembly.GetExceutingAssembly.Location) bzw. hier wäre auch GetCallingAssembly möglich wenn die Loggin-Komponente in der Klassenbibliothek liegt.

Somit sollte der Code lauten:

using System;
using System.IO;
using System.Reflection;

namespace LogFileWriter
{
	/// <summary>
	/// Klasse zum schreiben eines LogFiles, mit Multithreaded Unterstützung.
	/// </summary>
	public class LogWriter
	{
		private static LogWriter _instance = null;

		// static nicht unbedingt notwendig da nur eine Instanz des Singleton
		// existieren soll:
		private static readonly object _syncLock = new object();
		
		private readonly string _logName = Path.Combine(
			Path.GetDirectoryName(Assembly.GetCallingAssembly().Location),
			@"\log\logfile.txt");
		//---------------------------------------------------------------------
		public static LogWriter GetInstance()
		{
			// Double-check locking
			if (_instance == null)
				lock (_syncLock)
					if (_instance == null)
						_instance = new LogWriter();

			return _instance;
		}
		//---------------------------------------------------------------------
		/// <summary>
		/// Privater Konstruktor für Singleton.
		/// </summary>
		private LogWriter()
		{
		}
		//---------------------------------------------------------------------
		/// <summary>
		/// Loggt eine Exception
		/// </summary>
		/// <param name="exception"></param>
		public void LogException(Exception exception)
		{
			string msg = exception.Message + " (" + exception.Source + ")";
			LogMessage(msg, "Fehler");
		}
		//---------------------------------------------------------------------
		/// <summary>
		/// Schreibt eine Nachricht ins Logfile
		/// </summary>
		/// <param name="message">Die zu loggende Nachricht</param>
		/// <param name="severity">Nachrichtenkategorie (z.B. Info, Fehler, etc.)</param>
		public void LogMessage(string message, string severity)
		{
			string body = DateTime.Now.ToString("HH:mm:ss")
				+ " - "
				+ severity
				+ ":"
				+ Environment.NewLine + message;

			lock (_syncLock)
			{
				if (!Directory.Exists(Path.GetDirectoryName(_logName)))
					Directory.CreateDirectory(Path.GetDirectoryName(_logName));

				if (!File.Exists(_logName))
				{
					body = "Logfile erstellt am "
						+ DateTime.Now.ToShortDateString()
						+ " um "
						+ DateTime.Now.ToShortTimeString()
						+ Environment.NewLine
						+ Environment.NewLine
						+ body;
				}

				try
				{
					using (StreamWriter sw = new StreamWriter(_logName, true))
					{
						sw.WriteLine(body);
					}
				}
				catch (Exception e)
				{
					throw e;
				}
			}
		}
	}
}

Den Teil zwischen lock(..) hab ich nicht geändert.

mfG Günther

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

3.003 Beiträge seit 2006
vor 14 Jahren

Besser. Dem Singleton sieht man allerdings noch ganz gut an, dass die Implementierung wohl aus der Java-Welt entnommen ist. Empfehle Variante 2, besser 4 (Variante 5 wäre wohl zuviel des Guten) der hier beschriebenen C#-spezifischen Singleton-Implementierung.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

Gelöschter Account
vor 14 Jahren

@t2t

                catch (Exception e)
                {
                    throw e;
                }

wenn du eine exception nciht behandeln kannst, dann fang sie erst garnicht.

zudem bietet deine Singleton klasse keinerlei multithread unterstützung. lediglich das lazy instanciating ist threadsafe aber das ist nur der allererste zugriff.

6.911 Beiträge seit 2009
vor 14 Jahren

Hast recht - entweder mit **volatile **oder eben auf "eager creation" umstellen.

private static volatile LogWriter _instance = null;

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

6.911 Beiträge seit 2009
vor 14 Jahren

@t2t

                catch (Exception e)  
                {  
                    throw e;  
                }  
  

wenn du eine exception nciht behandeln kannst, dann fang sie erst garnicht.

Anmerkung:
throw e sollte sowieso nicht verwendet werden denn dabei wird der Stacktrace rückgesetzt. Ein einfaches **throw **reicht oder mittels InnerException.

zudem bietet deine Singleton klasse keinerlei multithread unterstützung. lediglich das lazy instanciating ist threadsafe aber das ist nur der allererste zugriff.

Siehe meinen ersten Beitrag.

mfG Günther

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

3.003 Beiträge seit 2006
vor 14 Jahren
private static volatile LogWriter _instance = null;  

Making the instance variable volatile can make it work, as would explicit memory barrier calls, although in the latter case even experts can't agree exactly which barriers are required. I tend to try to avoid situations where experts don't agree what's right and what's wrong!

Dann lieber eager, zumal mir hier kein Vorteil an der lazy instantiation einfällt. Egal, Krümel und so 😉.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)