Index: /branches/eraser6/CodeReview/Eraser/SchedulerPanel.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser/SchedulerPanel.cs	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser/SchedulerPanel.cs	(revision 1777)
@@ -22,5 +22,5 @@
 using System;
 using System.Collections.Generic;
-using System.Data;
+using System.Linq;
 using System.Drawing;
 using System.Text;
@@ -252,16 +252,8 @@
 
 			//Get the exit status of the task.
-			LogLevel highestLevel = LogLevel.Information;
-			LogEntryCollection logs = task.Log.LastSessionEntries;
-			foreach (LogEntry log in logs)
-				if (log.Level > highestLevel)
-					highestLevel = log.Level;
+			LogLevel highestLevel = task.Log.Last().Highest;
 
 			//Show a balloon to inform the user
 			MainForm parent = (MainForm)FindForm();
-
-			//TODO: Is this still needed?
-			if (parent == null)
-				throw new InvalidOperationException();
 			if (parent.WindowState == FormWindowState.Minimized || !parent.Visible)
 			{
Index: /branches/eraser6/CodeReview/Eraser/LogForm.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser/LogForm.cs	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser/LogForm.cs	(revision 1777)
@@ -23,5 +23,5 @@
 using System.Collections.Generic;
 using System.ComponentModel;
-using System.Data;
+using System.Linq;
 using System.Drawing;
 using System.Text;
@@ -35,5 +35,5 @@
 namespace Eraser
 {
-	public partial class LogForm : Form
+	public partial class LogForm : Form, ILogTarget
 	{
 		public LogForm(Task task)
@@ -46,7 +46,7 @@
 
 			//Populate the list of sessions
-			foreach (DateTime session in task.Log.Entries.Keys)
-				filterSessionCombobox.Items.Add(session);
-			if (task.Log.Entries.Keys.Count != 0)
+			foreach (LogSink sink in task.Log)
+				filterSessionCombobox.Items.Add(sink.StartTime);
+			if (filterSessionCombobox.Items.Count != 0)
 				filterSessionCombobox.SelectedIndex = filterSessionCombobox.Items.Count - 1;
 
@@ -61,12 +61,14 @@
 
 			//Register our event handler to get live log messages
-			Task.Log.Logged += task_Logged;
-			Task.Log.NewSession += task_NewSession;
+			if (Task.Log.Count > 0)
+				Task.Log.Last().Chain(this);
+			Task.TaskStarted += task_TaskStarted;
 		}
 
 		private void LogForm_FormClosed(object sender, FormClosedEventArgs e)
 		{
-			Task.Log.NewSession -= task_NewSession;
-			Task.Log.Logged -= task_Logged;
+			Task.TaskStarted -= task_TaskStarted;
+			if (Task.Log.Count > 0)
+				Task.Log.Last().Unchain(this);
 		}
 
@@ -76,5 +78,5 @@
 		}
 
-		private void task_NewSession(object sender, EventArgs e)
+		private void task_TaskStarted(object sender, EventArgs e)
 		{
 			if (IsDisposed || !IsHandleCreated)
@@ -82,37 +84,9 @@
 			if (InvokeRequired)
 			{
-				Invoke((EventHandler<EventArgs>)task_NewSession, sender, e);
-				return;
-			}
-
-			filterSessionCombobox.Items.Add(Task.Log.LastSession);
-		}
-
-		private void task_Logged(object sender, LogEventArgs e)
-		{
-			if (IsDisposed || !IsHandleCreated)
-				return;
-			if (InvokeRequired)
-			{
-				Invoke((EventHandler<LogEventArgs>)task_Logged, sender, e);
-				return;
-			}
-
-			//Check whether the current entry meets the criteria for display. Since
-			//this is an event handler for new log messages only, we should only
-			//display this entry when the session in question is the last one.
-			if (filterSessionCombobox.SelectedItem == null ||
-				(DateTime)filterSessionCombobox.SelectedItem != Task.Log.LastSession ||
-				!MeetsCriteria(e.LogEntry))
-			{
-				return;
-			}
-
-			//Add it to the cache and increase our virtual list size.
-			EntryCache.Add(e.LogEntry);
-			++log.VirtualListSize;
-
-			//Enable the clear and copy log buttons only if we have entries to copy.
-			EnableButtons();
+				Invoke((EventHandler<EventArgs>)task_TaskStarted, sender, e);
+				return;
+			}
+
+			filterSessionCombobox.Items.Add(Task.Log.Last().StartTime);
 		}
 
@@ -265,4 +239,42 @@
 		}
 
+		#region ILogTarget Members
+
+		public void OnEventLogged(object sender, LogEventArgs e)
+		{
+			if (IsDisposed || !IsHandleCreated)
+				return;
+			if (InvokeRequired)
+			{
+				Invoke((EventHandler<LogEventArgs>)OnEventLogged, sender, e);
+				return;
+			}
+
+			//Check whether the current entry meets the criteria for display.
+			if (filterSessionCombobox.SelectedItem == null || !MeetsCriteria(e.LogEntry))
+			{
+				return;
+			}
+
+			//Add it to the cache and increase our virtual list size.
+			EntryCache.Add(e.LogEntry);
+			++log.VirtualListSize;
+
+			//Enable the clear and copy log buttons only if we have entries to copy.
+			EnableButtons();
+		}
+
+		public void Chain(ILogTarget target)
+		{
+			throw new NotImplementedException();
+		}
+
+		public void Unchain(ILogTarget target)
+		{
+			throw new NotImplementedException();
+		}
+
+		#endregion
+
 		/// <summary>
 		/// Checks whether the given log entry meets the current display criteria.
@@ -305,27 +317,12 @@
 
 			Application.UseWaitCursor = true;
-			LogSessionDictionary log = Task.Log.Entries;
+			LogSink sink = Task.Log[filterSessionCombobox.SelectedIndex];
 			EntryCache.Clear();
 			SelectedEntries.Clear();
-
-			//Iterate over every key
-			foreach (DateTime sessionTime in log.Keys)
-			{
-				//Check for the session time
-				if (filterSessionCombobox.SelectedItem == null || 
-					sessionTime != (DateTime)filterSessionCombobox.SelectedItem)
-					continue;
-
-				foreach (LogEntry entry in log[sessionTime])
-				{
-					//Check if the entry meets the criteria for viewing
-					if (MeetsCriteria(entry))
-						EntryCache.Add(entry);
-				}
-			}
+			EntryCache.AddRange(sink.Where(MeetsCriteria));
 
 			//Set the list view size and update all the control states
-			this.log.VirtualListSize = EntryCache.Count;
-			this.log.Refresh();
+			log.VirtualListSize = EntryCache.Count;
+			log.Refresh();
 			EnableButtons();
 			Application.UseWaitCursor = false;
@@ -337,5 +334,5 @@
 		private void EnableButtons()
 		{
-			clear.Enabled = Task.Log.Entries.Count > 0;
+			clear.Enabled = Task.Log.Count > 0;
 		}
 
Index: /branches/eraser6/CodeReview/Eraser/ProgressForm.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser/ProgressForm.cs	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser/ProgressForm.cs	(revision 1777)
@@ -22,5 +22,5 @@
 using System;
 using System.Collections.Generic;
-using System.Data;
+using System.Linq;
 using System.Drawing;
 using System.Text;
@@ -109,10 +109,5 @@
 
 			//Inform the user on the status of the task.
-			LogLevel highestLevel = LogLevel.Information;
-			LogEntryCollection entries = task.Log.LastSessionEntries;
-			foreach (LogEntry log in entries)
-				if (log.Level > highestLevel)
-					highestLevel = log.Level;
-
+			LogLevel highestLevel = task.Log.Last().Highest;
 			switch (highestLevel)
 			{
Index: /branches/eraser6/CodeReview/Eraser.Manager/FileSystem.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser.Manager/FileSystem.cs	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser.Manager/FileSystem.cs	(revision 1777)
@@ -203,6 +203,5 @@
 		/// <param name="eraseCallback">The callback function for erasure progress.</param>
 		public abstract void EraseClusterTips(VolumeInfo info, ErasureMethod method,
-			Logger log, ClusterTipsSearchProgress searchCallback,
-			ClusterTipsEraseProgress eraseCallback);
+			ClusterTipsSearchProgress searchCallback, ClusterTipsEraseProgress eraseCallback);
 
 		/// <summary>
Index: /branches/eraser6/CodeReview/Eraser.Manager/Eraser.Manager.csproj
===================================================================
--- /branches/eraser6/CodeReview/Eraser.Manager/Eraser.Manager.csproj	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser.Manager/Eraser.Manager.csproj	(revision 1777)
@@ -71,5 +71,4 @@
     <Compile Include="FileSystem.cs" />
     <Compile Include="Language.cs" />
-    <Compile Include="Logger.cs" />
     <Compile Include="ManagerLibrary.cs" />
     <Compile Include="Method.cs" />
Index: /branches/eraser6/CodeReview/Eraser.Manager/DirectExecutor.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser.Manager/DirectExecutor.cs	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser.Manager/DirectExecutor.cs	(revision 1777)
@@ -255,89 +255,14 @@
 					currentTask = task;
 
-						//Prevent the system from sleeping.
-						Power.ExecutionState = ExecutionState.Continuous |
-							ExecutionState.SystemRequired;
+					//Prevent the system from sleeping.
+					Power.ExecutionState = ExecutionState.Continuous | ExecutionState.SystemRequired;
 
 					//Start a new log session to separate this session's events
 					//from previous ones.
-					task.Log.Entries.NewSession();
-
-					try
-					{
-						//Broadcast the task started event.
-						task.Canceled = false;
-						task.OnTaskStarted();
-
-						//Run the task
-						foreach (ErasureTarget target in task.Targets)
-							try
-							{
-								UnusedSpaceTarget unusedSpaceTarget =
-									target as UnusedSpaceTarget;
-								FileSystemObjectTarget fileSystemObjectTarget =
-									target as FileSystemObjectTarget;
-
-								if (unusedSpaceTarget != null)
-									EraseUnusedSpace(task, unusedSpaceTarget);
-								else if (fileSystemObjectTarget != null)
-									EraseFilesystemObject(task, fileSystemObjectTarget);
-								else
-									throw new ArgumentException("Unknown erasure target.");
-							}
-							catch (FatalException)
-							{
-								throw;
-							}
-							catch (OperationCanceledException)
-							{
-								throw;
-							}
-							catch (ThreadAbortException)
-							{
-							}
-							catch (Exception e)
-							{
-								task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error));
-								BlackBox.Get().CreateReport(e);
-							}
-					}
-					catch (FatalException e)
-					{
-						task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Fatal));
-					}
-					catch (OperationCanceledException e)
-					{
-						task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Fatal));
-					}
-					catch (ThreadAbortException)
-					{
-						//Do nothing. The exception will be rethrown after this block
-						//is executed. This is here mainly to ensure that no BlackBox
-						//report is created for this exception.
-					}
-					catch (Exception e)
-					{
-						task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error));
-						BlackBox.Get().CreateReport(e);
-					}
-					finally
-					{
-						//Allow the system to sleep again.
-						Power.ExecutionState = ExecutionState.Continuous;
-
-						//If the task is a recurring task, reschedule it since we are done.
-						if (task.Schedule is RecurringSchedule)
-							((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
-
-						//If the task is an execute on restart task, it is only run
-						//once and can now be restored to an immediately executed task
-						if (task.Schedule == Schedule.RunOnRestart)
-							task.Schedule = Schedule.RunNow;
-
-						//And the task finished event.
-						task.OnTaskFinished();
-
-						//Remove the actively executing task from our instance variable
-						currentTask = null;
+					LogSink sessionLog = new LogSink();
+					task.Log.Add(sessionLog);
+					using (new LogSession(sessionLog))
+					{
+						ExecuteTask(task);
 					}
 				}
@@ -345,4 +270,90 @@
 				//Wait for half a minute to check for the next scheduled task.
 				schedulerInterrupt.WaitOne(30000, false);
+			}
+		}
+
+		/// <summary>
+		/// Executes the given task.
+		/// </summary>
+		/// <param name="task">The task to execute.</param>
+		private void ExecuteTask(Task task)
+		{
+			try
+			{
+				//Broadcast the task started event.
+				task.Canceled = false;
+				task.OnTaskStarted();
+
+				//Run the task
+				foreach (ErasureTarget target in task.Targets)
+					try
+					{
+						UnusedSpaceTarget unusedSpaceTarget =
+							target as UnusedSpaceTarget;
+						FileSystemObjectTarget fileSystemObjectTarget =
+							target as FileSystemObjectTarget;
+
+						if (unusedSpaceTarget != null)
+							EraseUnusedSpace(task, unusedSpaceTarget);
+						else if (fileSystemObjectTarget != null)
+							EraseFilesystemObject(task, fileSystemObjectTarget);
+						else
+							throw new ArgumentException("Unknown erasure target.");
+					}
+					catch (FatalException)
+					{
+						throw;
+					}
+					catch (OperationCanceledException)
+					{
+						throw;
+					}
+					catch (ThreadAbortException)
+					{
+					}
+					catch (Exception e)
+					{
+						Logger.Log(e.Message, LogLevel.Error);
+						BlackBox.Get().CreateReport(e);
+					}
+			}
+			catch (FatalException e)
+			{
+				Logger.Log(e.Message, LogLevel.Fatal);
+			}
+			catch (OperationCanceledException e)
+			{
+				Logger.Log(e.Message, LogLevel.Fatal);
+			}
+			catch (ThreadAbortException)
+			{
+				//Do nothing. The exception will be rethrown after this block
+				//is executed. This is here mainly to ensure that no BlackBox
+				//report is created for this exception.
+			}
+			catch (Exception e)
+			{
+				Logger.Log(e.Message, LogLevel.Error);
+				BlackBox.Get().CreateReport(e);
+			}
+			finally
+			{
+				//Allow the system to sleep again.
+				Power.ExecutionState = ExecutionState.Continuous;
+
+				//If the task is a recurring task, reschedule it since we are done.
+				if (task.Schedule is RecurringSchedule)
+					((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
+
+				//If the task is an execute on restart task, it is only run
+				//once and can now be restored to an immediately executed task
+				if (task.Schedule == Schedule.RunOnRestart)
+					task.Schedule = Schedule.RunNow;
+
+				//And the task finished event.
+				task.OnTaskFinished();
+
+				//Remove the actively executing task from our instance variable
+				currentTask = null;
 			}
 		}
@@ -361,13 +372,12 @@
 					Environment.OSVersion.Version >= new Version(6, 0))
 				{
-					task.Log.LastSessionEntries.Add(new LogEntry(S._("The program does not have " +
-						"the required permissions to erase the unused space on disk. Run the " +
-						"program as an administrator and retry the operation."), LogLevel.Error));
+					Logger.Log(S._("The program does not have the required permissions to erase " +
+						"the unused space on disk. Run the program as an administrator and retry " +
+						"the operation."), LogLevel.Error);
 				}
 				else
 				{
-					task.Log.LastSessionEntries.Add(new LogEntry(S._("The program does not have " +
-						"the required permissions to erase the unused space on disk."),
-						LogLevel.Error));
+					Logger.Log(S._("The program does not have the required permissions to erase " +
+						"the unused space on disk."), LogLevel.Error);
 				}
 			}
@@ -376,15 +386,14 @@
 			if (SystemRestore.GetInstances().Count != 0)
 			{
-				task.Log.LastSessionEntries.Add(new LogEntry(S._("The drive {0} has System " +
-					"Restore or Volume Shadow Copies enabled. This may allow copies of files " +
-					"stored on the disk to be recovered and pose a security concern.",
-					target.Drive), LogLevel.Warning));
+				Logger.Log(S._("The drive {0} has System Restore or Volume Shadow Copies " +
+					"enabled. This may allow copies of files stored on the disk to be recovered " +
+					"and pose a security concern.", target.Drive), LogLevel.Warning);
 			}
 			
 			//If the user is under disk quotas, log a warning message
 			if (VolumeInfo.FromMountPoint(target.Drive).HasQuota)
-				task.Log.LastSessionEntries.Add(new LogEntry(S._("The drive {0} has disk quotas " +
-					"active. This will prevent the complete erasure of unused space and may pose " +
-					"a security concern.", target.Drive), LogLevel.Warning));
+				Logger.Log(S._("The drive {0} has disk quotas active. This will prevent the " +
+					"complete erasure of unused space and may pose a security concern.",
+					target.Drive), LogLevel.Warning);
 
 			//Get the erasure method if the user specified he wants the default.
@@ -439,5 +448,5 @@
 				//Start counting statistics
 				fsManager.EraseClusterTips(VolumeInfo.FromMountPoint(target.Drive),
-					method, task.Log, searchProgress, eraseProgress);
+					method, searchProgress, eraseProgress);
 				tipProgress.MarkComplete();
 			}
@@ -608,6 +617,6 @@
 				if (!info.Exists)
 				{
-					task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} was not erased " +
-						"as the file does not exist.", paths[i]), LogLevel.Notice));
+					Logger.Log(S._("The file {0} was not erased as the file does not exist.",
+						paths[i]), LogLevel.Notice);
 					continue;
 				}
@@ -632,7 +641,7 @@
 					{
 						//Log the error
-						task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} could " +
-							"not be erased because the file was either compressed, encrypted or " +
-							"a sparse file.", info.FullName), LogLevel.Error));
+						Logger.Log(S._("The file {0} could not be erased because the file was " +
+							"either compressed, encrypted or a sparse file.", info.FullName),
+							LogLevel.Error);
 						continue;
 					}
@@ -659,7 +668,6 @@
 				catch (UnauthorizedAccessException)
 				{
-					task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} could not " +
-						"be erased because the file's permissions prevent access to the file.",
-						info.FullName), LogLevel.Error));
+					Logger.Log(S._("The file {0} could not be erased because the file's " +
+						"permissions prevent access to the file.", info.FullName), LogLevel.Error);
 				}
 				catch (IOException)
@@ -696,7 +704,6 @@
 						}
 
-						task.Log.LastSessionEntries.Add(new LogEntry(S._(
-							"Could not force closure of file \"{0}\" {1}", paths[i],
-							lockedBy == null ? string.Empty : lockedBy).Trim(), LogLevel.Error));
+						Logger.Log(S._("Could not force closure of file \"{0}\" {1}", paths[i],
+							lockedBy == null ? string.Empty : lockedBy).Trim(), LogLevel.Error);
 					}
 					else
Index: /branches/eraser6/CodeReview/Eraser.Manager/Task.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser.Manager/Task.cs	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser.Manager/Task.cs	(revision 1777)
@@ -47,5 +47,5 @@
 			Targets = (ErasureTargetsCollection)info.GetValue("Targets", typeof(ErasureTargetsCollection));
 			Targets.Owner = this;
-			Log = (Logger)info.GetValue("Log", typeof(Logger));
+			Log = (List<LogSink>)info.GetValue("Log", typeof(List<LogSink>));
 			Canceled = false;
 
@@ -83,5 +83,5 @@
 			Schedule = Schedule.RunNow;
 			Canceled = false;
-			Log = new Logger();
+			Log = new List<LogSink>();
 		}
 
@@ -198,5 +198,5 @@
 		/// The log entries which this task has accumulated.
 		/// </summary>
-		public Logger Log { get; private set; }
+		public List<LogSink> Log { get; private set; }
 
 		/// <summary>
@@ -481,5 +481,5 @@
 				//The system cannot read the file, assume no ADSes for lack of
 				//more information.
-				Task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error));
+				Logger.Log(e.Message, LogLevel.Error);
 			}
 		}
@@ -743,6 +743,6 @@
 				catch (UnauthorizedAccessException e)
 				{
-					Task.Log.LastSessionEntries.Add(new LogEntry(S._("Could not erase files and " +
-						"subfolders in {0} because {1}", info.FullName, e.Message), LogLevel.Error));
+					Logger.Log(S._("Could not erase files and subfolders in {0} because {1}",
+						info.FullName, e.Message), LogLevel.Error);
 				}
 			}
@@ -840,5 +840,5 @@
 			catch (UnauthorizedAccessException e)
 			{
-				Task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error));
+				Logger.Log(e.Message, LogLevel.Error);
 			}
 		}
Index: anches/eraser6/CodeReview/Eraser.Manager/Logger.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser.Manager/Logger.cs	(revision 1776)
+++ 	(revision )
@@ -1,532 +1,0 @@
-/* 
- * $Id$
- * Copyright 2008-2010 The Eraser Project
- * Original Author: Joel Low <lowjoel@users.sourceforge.net>
- * Modified By:
- * 
- * This file is part of Eraser.
- * 
- * Eraser is free software: you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option) any later
- * version.
- * 
- * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- * 
- * A copy of the GNU General Public License can be found at
- * <http://www.gnu.org/licenses/>.
- */
-
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Runtime.Serialization;
-using System.Security.Permissions;
-
-namespace Eraser.Manager
-{
-	/// <summary>
-	/// The levels of logging allowing for the filtering of messages.
-	/// </summary>
-	public enum LogLevel
-	{
-		/// <summary>
-		/// Informative messages.
-		/// </summary>
-		Information,
-
-		/// <summary>
-		/// Notice messages.
-		/// </summary>
-		Notice,
-
-		/// <summary>
-		/// Warning messages.
-		/// </summary>
-		Warning,
-
-		/// <summary>
-		/// Error messages.
-		/// </summary>
-		Error,
-
-		/// <summary>
-		/// Fatal errors.
-		/// </summary>
-		Fatal
-	}
-
-	/// <summary>
-	/// The Logger class which handles log entries and manages entries.
-	/// 
-	/// The class has the notion of entries and sessions. Each session contains one
-	/// or more (log) entries. This allows the program to determine if the last
-	/// session had errors or not.
-	/// </summary>
-	[Serializable]
-	public class Logger : ISerializable
-	{
-		#region Serialization code
-		protected Logger(SerializationInfo info, StreamingContext context)
-		{
-			Entries = (LogSessionDictionary)info.GetValue("Entries", typeof(LogSessionDictionary));
-			Entries.Owner = this;
-			foreach (DateTime key in Entries.Keys)
-				LastSession = key;
-		}
-
-		[SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
-		public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
-		{
-			info.AddValue("Entries", Entries);
-		}
-		#endregion
-
-		/// <summary>
-		/// Constructor.
-		/// </summary>
-		public Logger()
-		{
-			Entries = new LogSessionDictionary(this);
-		}
-
-		/// <summary>
-		/// All the registered event handlers for the log event of this task.
-		/// </summary>
-		public EventHandler<LogEventArgs> Logged { get; set; }
-
-		internal void OnLogged(object sender, LogEventArgs e)
-		{
-			if (Logged != null)
-				Logged(sender, e);
-		}
-
-		/// <summary>
-		/// All the registered event handlers for handling when a new session has been
-		/// started.
-		/// </summary>
-		public EventHandler<EventArgs> NewSession { get; set; }
-
-		internal void OnNewSession(object sender, EventArgs e)
-		{
-			if (NewSession != null)
-				NewSession(sender, e);
-		}
-
-		/// <summary>
-		/// Retrieves the log for this task.
-		/// </summary>
-		public LogSessionDictionary Entries { get; private set; }
-
-		/// <summary>
-		/// Retrieves the log entries from the previous session.
-		/// </summary>
-		public LogEntryCollection LastSessionEntries
-		{
-			get
-			{
-				return Entries[LastSession];
-			}
-		}
-
-		/// <summary>
-		/// Clears the log entries from the log.
-		/// </summary>
-		public void Clear()
-		{
-			LogEntryCollection lastSessionEntries = null;
-			if (Entries.ContainsKey(LastSession))
-				lastSessionEntries = Entries[LastSession];
-			Entries.Clear();
-
-			if (lastSessionEntries != null)
-				Entries.Add(LastSession, lastSessionEntries);
-		}
-
-		/// <summary>
-		/// The date and time of the last session.
-		/// </summary>
-		public DateTime LastSession
-		{
-			get { return lastSession; }
-			internal set { lastSession = value; OnNewSession(null, EventArgs.Empty); }
-		}
-
-		private DateTime lastSession;
-	}
-
-	public class LogEventArgs : EventArgs
-	{
-		/// <summary>
-		/// Constructor.
-		/// </summary>
-		/// <param name="entry">The log entry that was just logged.</param>
-		public LogEventArgs(LogEntry entry)
-		{
-			LogEntry = entry;
-		}
-
-		/// <summary>
-		/// The log entry that was just logged.
-		/// </summary>
-		public LogEntry LogEntry { get; private set; }
-	}
-
-	[Serializable]
-	public class LogSessionDictionary : IDictionary<DateTime, LogEntryCollection>,
-		ISerializable
-	{
-		/// <summary>
-		/// Constructor.
-		/// </summary>
-		/// <param name="logger">The logger object managing the logging.</param>
-		public LogSessionDictionary(Logger logger)
-		{
-			Owner = logger;
-		}
-
-		public void NewSession()
-		{
-			DateTime sessionTime = DateTime.Now;
-			Add(sessionTime, new LogEntryCollection(Owner));
-			Owner.LastSession = sessionTime;
-		}
-
-		#region ISerializable Members
-		protected LogSessionDictionary(SerializationInfo info, StreamingContext context)
-		{
-			dictionary = (Dictionary<DateTime, LogEntryCollection>)info.GetValue("Dictionary",
-				dictionary.GetType());
-		}
-
-		[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
-		public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
-		{
-			lock (dictionary)
-				info.AddValue("Dictionary", dictionary);
-		}
-		#endregion
-
-		#region IDictionary<DateTime,LogSessionEntryCollection> Members
-		public void Add(DateTime key, LogEntryCollection value)
-		{
-			lock (dictionary)
-				dictionary.Add(key, value);
-		}
-
-		public bool ContainsKey(DateTime key)
-		{
-			lock (dictionary)
-				return dictionary.ContainsKey(key);
-		}
-
-		public ICollection<DateTime> Keys
-		{
-			get
-			{
-				lock (dictionary)
-				{
-					DateTime[] result = new DateTime[dictionary.Keys.Count];
-					dictionary.Keys.CopyTo(result, 0);
-					return result;
-				}
-			}
-		}
-
-		public bool Remove(DateTime key)
-		{
-			lock (dictionary)
-				return dictionary.Remove(key);
-		}
-
-		public bool TryGetValue(DateTime key, out LogEntryCollection value)
-		{
-			lock (dictionary)
-				return dictionary.TryGetValue(key, out value);
-		}
-
-		public ICollection<LogEntryCollection> Values
-		{
-			get
-			{
-				lock (dictionary)
-				{
-					LogEntryCollection[] result = new LogEntryCollection[dictionary.Values.Count];
-					dictionary.Values.CopyTo(result, 0);
-					return result;
-				}
-			}
-		}
-
-		public LogEntryCollection this[DateTime key]
-		{
-			get
-			{
-				lock (dictionary)
-					return dictionary[key];
-			}
-			set
-			{
-				lock (dictionary)
-					dictionary[key] = value;
-			}
-		}
-		#endregion
-
-		#region ICollection<KeyValuePair<DateTime,LogSessionEntryCollection>> Members
-		public void Add(KeyValuePair<DateTime, LogEntryCollection> item)
-		{
-			Add(item.Key, item.Value);
-		}
-
-		public void Clear()
-		{
-			lock (dictionary)
-				dictionary.Clear();
-		}
-
-		public bool Contains(KeyValuePair<DateTime, LogEntryCollection> item)
-		{
-			lock (dictionary)
-				return dictionary.ContainsKey(item.Key) && dictionary[item.Key] == item.Value;
-		}
-
-		public void CopyTo(KeyValuePair<DateTime, LogEntryCollection>[] array, int arrayIndex)
-		{
-			throw new NotImplementedException();
-		}
-
-		public int Count
-		{
-			get
-			{
-				lock (dictionary)
-					return dictionary.Count;
-			}
-		}
-
-		public bool IsReadOnly
-		{
-			get { return false; }
-		}
-
-		public bool Remove(KeyValuePair<DateTime, LogEntryCollection> item)
-		{
-			lock (dictionary)
-				return dictionary.Remove(item.Key);
-		}
-		#endregion
-
-		#region IEnumerable<KeyValuePair<DateTime,LogSessionEntryCollection>> Members
-		public IEnumerator<KeyValuePair<DateTime, LogEntryCollection>> GetEnumerator()
-		{
-			return dictionary.GetEnumerator();
-		}
-		#endregion
-
-		#region IEnumerable Members
-		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
-		{
-			return GetEnumerator();
-		}
-		#endregion
-
-		/// <summary>
-		/// The log manager.
-		/// </summary>
-		internal Logger Owner
-		{
-			get
-			{
-				return owner;
-			}
-			set
-			{
-				lock (dictionary)
-					foreach (LogEntryCollection entries in dictionary.Values)
-						entries.owner = value;
-				owner = value;
-			}
-		}
-
-		/// <summary>
-		/// The log manager.
-		/// </summary>
-		private Logger owner;
-
-		/// <summary>
-		/// The store for this object.
-		/// </summary>
-		private Dictionary<DateTime, LogEntryCollection> dictionary =
-			new Dictionary<DateTime, LogEntryCollection>();
-	}
-
-	[Serializable]
-	public class LogEntryCollection : IList<LogEntry>
-	{
-		/// <summary>
-		/// Constructor.
-		/// </summary>
-		/// <param name="logger">The <see cref="Logger"/> object handling logging.</param>
-		internal LogEntryCollection(Logger logger)
-		{
-			owner = logger;
-		}
-
-		#region IList<LogEntry> Members
-		public int IndexOf(LogEntry item)
-		{
-			lock (list)
-				return list.IndexOf(item);
-		}
-
-		public void Insert(int index, LogEntry item)
-		{
-			lock (list)
-				list.Insert(index, item);
-			owner.OnLogged(owner, new LogEventArgs(item));
-		}
-
-		public void RemoveAt(int index)
-		{
-			throw new InvalidOperationException();
-		}
-
-		public LogEntry this[int index]
-		{
-			get
-			{
-				lock (list)
-					return list[index];
-			}
-			set
-			{
-				throw new InvalidOperationException();
-			}
-		}
-		#endregion
-
-		#region ICollection<LogEntry> Members
-		public void Add(LogEntry item)
-		{
-			Insert(Count, item);
-		}
-
-		public void Clear()
-		{
-			throw new InvalidOperationException();
-		}
-
-		public bool Contains(LogEntry item)
-		{
-			lock (list)
-				return list.Contains(item);
-		}
-
-		public void CopyTo(LogEntry[] array, int arrayIndex)
-		{
-			lock (list)
-				list.CopyTo(array, arrayIndex);
-		}
-
-		public int Count
-		{
-			get
-			{
-				lock (list)
-					return list.Count;
-			}
-		}
-
-		public bool IsReadOnly
-		{
-			get { return false; }
-		}
-
-		public bool Remove(LogEntry item)
-		{
-			lock (list)
-				return list.Remove(item);
-		}
-		#endregion
-
-		#region IEnumerable<LogEntry> Members
-		public IEnumerator<LogEntry> GetEnumerator()
-		{
-			return list.GetEnumerator();
-		}
-		#endregion
-
-		#region IEnumerable Members
-		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
-		{
-			return GetEnumerator();
-		}
-		#endregion
-
-		/// <summary>
-		/// The Logger object managing logging.
-		/// </summary>
-		internal Logger owner;
-
-		/// <summary>
-		/// The store for this object.
-		/// </summary>
-		private List<LogEntry> list = new List<LogEntry>();
-	}
-
-	/// <summary>
-	/// Represents a log entry.
-	/// </summary>
-	[Serializable]
-	public struct LogEntry : ISerializable
-	{
-		#region Serialization code
-		private LogEntry(SerializationInfo info, StreamingContext context)
-			: this()
-		{
-			Level = (LogLevel)info.GetValue("Level", typeof(LogLevel));
-			Timestamp = (DateTime)info.GetValue("Timestamp", typeof(DateTime));
-			Message = (string)info.GetValue("Message", typeof(string));
-		}
-
-		[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
-		public void GetObjectData(SerializationInfo info, StreamingContext context)
-		{
-			info.AddValue("Level", Level);
-			info.AddValue("Timestamp", Timestamp);
-			info.AddValue("Message", Message);
-		}
-		#endregion
-
-		/// <summary>
-		/// Creates a LogEntry structure.
-		/// </summary>
-		/// <param name="message">The log message.</param>
-		/// <param name="level">The type of log entry.</param>
-		public LogEntry(string message, LogLevel level)
-			: this()
-		{
-			Message = message;
-			Level = level;
-			Timestamp = DateTime.Now;
-		}
-
-		/// <summary>
-		/// The type of log entry.
-		/// </summary>
-		public LogLevel Level { get; private set; }
-
-		/// <summary>
-		/// The time which the message was logged.
-		/// </summary>
-		public DateTime Timestamp { get; private set; }
-
-		/// <summary>
-		/// The log message.
-		/// </summary>
-		public string Message { get; private set; }
-	}
-}
Index: /branches/eraser6/CodeReview/Eraser.Util/Logger.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser.Util/Logger.cs	(revision 1777)
+++ /branches/eraser6/CodeReview/Eraser.Util/Logger.cs	(revision 1777)
@@ -0,0 +1,738 @@
+﻿/* 
+ * $Id$
+ * Copyright 2008-2010 The Eraser Project
+ * Original Author: Joel Low <lowjoel@users.sourceforge.net>
+ * Modified By:
+ * 
+ * This file is part of Eraser.
+ * 
+ * Eraser is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ * 
+ * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ * 
+ * A copy of the GNU General Public License can be found at
+ * <http://www.gnu.org/licenses/>.
+ */
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Text;
+
+using System.Collections.ObjectModel;
+using System.Threading;
+using System.Runtime.Serialization;
+using System.Security.Permissions;
+
+namespace Eraser.Util
+{
+	/// <summary>
+	/// The levels of logging allowing for the filtering of messages.
+	/// </summary>
+	public enum LogLevel
+	{
+		/// <summary>
+		/// Informative messages.
+		/// </summary>
+		Information,
+
+		/// <summary>
+		/// Notice messages.
+		/// </summary>
+		Notice,
+
+		/// <summary>
+		/// Warning messages.
+		/// </summary>
+		Warning,
+
+		/// <summary>
+		/// Error messages.
+		/// </summary>
+		Error,
+
+		/// <summary>
+		/// Fatal errors.
+		/// </summary>
+		Fatal
+	}
+
+	/// <summary>
+	/// Represents a log entry.
+	/// </summary>
+	[Serializable]
+	public struct LogEntry : ISerializable
+	{
+		#region Serialization code
+		private LogEntry(SerializationInfo info, StreamingContext context)
+			: this()
+		{
+			Level = (LogLevel)info.GetValue("Level", typeof(LogLevel));
+			Timestamp = (DateTime)info.GetValue("Timestamp", typeof(DateTime));
+			Message = (string)info.GetValue("Message", typeof(string));
+		}
+
+		[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
+		public void GetObjectData(SerializationInfo info, StreamingContext context)
+		{
+			info.AddValue("Level", Level);
+			info.AddValue("Timestamp", Timestamp);
+			info.AddValue("Message", Message);
+		}
+		#endregion
+
+		/// <summary>
+		/// Creates a LogEntry structure.
+		/// </summary>
+		/// <param name="message">The log message.</param>
+		/// <param name="level">The type of log entry.</param>
+		public LogEntry(string message, LogLevel level)
+			: this()
+		{
+			Message = message;
+			Level = level;
+			Timestamp = DateTime.Now;
+		}
+
+		/// <summary>
+		/// The type of log entry.
+		/// </summary>
+		public LogLevel Level { get; private set; }
+
+		/// <summary>
+		/// The time which the message was logged.
+		/// </summary>
+		public DateTime Timestamp { get; private set; }
+
+		/// <summary>
+		/// The log message.
+		/// </summary>
+		public string Message { get; private set; }
+	}
+
+	/// <summary>
+	/// Event Data for all Logger events.
+	/// </summary>
+	public class LogEventArgs : EventArgs
+	{
+		/// <summary>
+		/// Constructor.
+		/// </summary>
+		/// <param name="entry">The log entry that was just logged.</param>
+		public LogEventArgs(LogEntry entry)
+		{
+			LogEntry = entry;
+		}
+
+		/// <summary>
+		/// The log entry that was just logged.
+		/// </summary>
+		public LogEntry LogEntry { get; private set; }
+	}
+
+	/// <summary>
+	/// Provides a standard logging interface to the rest of the Eraser classes.
+	/// </summary>
+	public static class Logger
+	{
+		static Logger()
+		{
+			Listeners = new LogThreadDictionary();
+		}
+
+		/// <summary>
+		/// Logs an informational message.
+		/// </summary>
+		/// <param name="message">The message to log.</param>
+		public static void Log(string message)
+		{
+			Log(new LogEntry(message, LogLevel.Information));
+		}
+
+		/// <summary>
+		/// Logs a message to the logger.
+		/// </summary>
+		/// <param name="message">The message to store.</param>
+		/// <param name="level">The level of the message.</param>
+		public static void Log(string message, LogLevel level)
+		{
+			Log(new LogEntry(message, level));
+		}
+
+		/// <summary>
+		/// Logs the provided entry to the logger.
+		/// </summary>
+		/// <param name="entry">The log entry to store.</param>
+		public static void Log(LogEntry entry)
+		{
+			Thread currentThread = Thread.CurrentThread;
+			List<ILogTarget> targets = new List<ILogTarget>();
+
+			if (Listeners.ContainsKey(currentThread))
+			{
+				LogThreadTargets threadTargets = Listeners[currentThread];
+				if (threadTargets != null)
+					targets.AddRange(threadTargets);
+			}
+
+			targets.ForEach(
+				target => target.OnEventLogged(currentThread, new LogEventArgs(entry)));
+		}
+
+		/// <summary>
+		/// The list of listeners for events on a particular thread.
+		/// </summary>
+		public static LogThreadDictionary Listeners { get; private set; }
+	}
+
+	/// <summary>
+	/// The Logger Thread Dictionary, which maps log event listeners to threads.
+	/// This mainly serves as a thread-safe Dictionary.
+	/// </summary>
+	public class LogThreadDictionary : IDictionary<Thread, LogThreadTargets>
+	{
+		#region IDictionary<Thread,LogThreadTargets> Members
+
+		public void Add(Thread key, LogThreadTargets value)
+		{
+			lock (Dictionary)
+				Dictionary.Add(key, value);
+		}
+
+		public bool ContainsKey(Thread key)
+		{
+			return Dictionary.ContainsKey(key);
+		}
+
+		public ICollection<Thread> Keys
+		{
+			get
+			{
+				lock (Dictionary)
+				{
+					Thread[] result = new Thread[Dictionary.Keys.Count];
+					Dictionary.Keys.CopyTo(result, 0);
+
+					return new ReadOnlyCollection<Thread>(result);
+				}
+			}
+		}
+
+		public bool Remove(Thread key)
+		{
+			lock (Dictionary)
+				return Dictionary.Remove(key);
+		}
+
+		public bool TryGetValue(Thread key, out LogThreadTargets value)
+		{
+			lock (Dictionary)
+				return Dictionary.TryGetValue(key, out value);
+		}
+
+		public ICollection<LogThreadTargets> Values
+		{
+			get
+			{
+				lock (Dictionary)
+				{
+					LogThreadTargets[] result =
+						new LogThreadTargets[Dictionary.Values.Count];
+					Dictionary.Values.CopyTo(result, 0);
+
+					return new ReadOnlyCollection<LogThreadTargets>(result);
+				}
+			}
+		}
+
+		public LogThreadTargets this[Thread key]
+		{
+			get
+			{
+				lock (Dictionary)
+					return Dictionary[key];
+			}
+			set
+			{
+				lock (Dictionary)
+					Dictionary[key] = value;
+			}
+		}
+
+		#endregion
+
+		#region ICollection<KeyValuePair<Thread,LogThreadTargets>> Members
+
+		public void Add(KeyValuePair<Thread, LogThreadTargets> item)
+		{
+			lock (Dictionary)
+				Dictionary.Add(item.Key, item.Value);
+		}
+
+		public void Clear()
+		{
+			lock (Dictionary)
+				Dictionary.Clear();
+		}
+
+		public bool Contains(KeyValuePair<Thread, LogThreadTargets> item)
+		{
+			lock (Dictionary)
+				return Dictionary.ContainsKey(item.Key) && Dictionary[item.Key] == item.Value;
+		}
+
+		public void CopyTo(KeyValuePair<Thread, LogThreadTargets>[] array, int arrayIndex)
+		{
+			throw new NotImplementedException();
+		}
+
+		public int Count
+		{
+			get
+			{
+				lock (Dictionary)
+					return Dictionary.Count;
+			}
+		}
+
+		public bool IsReadOnly
+		{
+			get { return false; }
+		}
+
+		public bool Remove(KeyValuePair<Thread, LogThreadTargets> item)
+		{
+			lock (Dictionary)
+				return Dictionary.Remove(item.Key);
+		}
+
+		#endregion
+
+		#region IEnumerable<KeyValuePair<Thread,LogThreadTargets>> Members
+
+		public IEnumerator<KeyValuePair<Thread, LogThreadTargets>> GetEnumerator()
+		{
+			return Dictionary.GetEnumerator();
+		}
+
+		#endregion
+
+		#region IEnumerable Members
+
+		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+		{
+			return Dictionary.GetEnumerator();
+		}
+
+		#endregion
+
+		/// <summary>
+		/// The backing store for this dictionary.
+		/// </summary>
+		private Dictionary<Thread, LogThreadTargets> Dictionary =
+			new Dictionary<Thread, LogThreadTargets>();
+	}
+
+	public class LogThreadTargets : IList<ILogTarget>
+	{
+		#region IList<ILogTarget> Members
+
+		public int IndexOf(ILogTarget item)
+		{
+			lock (List)
+				return List.IndexOf(item);
+		}
+
+		public void Insert(int index, ILogTarget item)
+		{
+			lock (List)
+				List.Insert(index, item);
+		}
+
+		public void RemoveAt(int index)
+		{
+			lock (List)
+				List.RemoveAt(index);
+		}
+
+		public ILogTarget this[int index]
+		{
+			get
+			{
+				lock (List)
+					return List[index];
+			}
+			set
+			{
+				lock (List)
+					List[index] = value;
+			}
+		}
+
+		#endregion
+
+		#region ICollection<ILogTarget> Members
+
+		public void Add(ILogTarget item)
+		{
+			lock (List)
+				List.Add(item);
+		}
+
+		public void Clear()
+		{
+			lock (List)
+				List.Clear();
+		}
+
+		public bool Contains(ILogTarget item)
+		{
+			lock (List)
+				return List.Contains(item);
+		}
+
+		public void CopyTo(ILogTarget[] array, int arrayIndex)
+		{
+			lock (List)
+				List.CopyTo(array, arrayIndex);
+		}
+
+		public int Count
+		{
+			get
+			{
+				lock (List)
+					return List.Count;
+			}
+		}
+
+		public bool IsReadOnly
+		{
+			get { return false; }
+		}
+
+		public bool Remove(ILogTarget item)
+		{
+			lock (List)
+				return List.Remove(item);
+		}
+
+		#endregion
+
+		#region IEnumerable<ILogTarget> Members
+
+		public IEnumerator<ILogTarget> GetEnumerator()
+		{
+			return List.GetEnumerator();
+		}
+
+		#endregion
+
+		#region IEnumerable Members
+
+		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+		{
+			return List.GetEnumerator();
+		}
+
+		#endregion
+
+		/// <summary>
+		/// The backing store for this list.
+		/// </summary>
+		private List<ILogTarget> List = new List<ILogTarget>();
+	}
+
+	/// <summary>
+	/// The logger target interface which all interested listeners of log events must
+	/// implement.
+	/// </summary>
+	public interface ILogTarget
+	{
+		/// <summary>
+		/// The handler for events.
+		/// </summary>
+		/// <param name="sender">The sender of the event.</param>
+		/// <param name="e">The event data associated with the event.</param>
+		void OnEventLogged(object sender, LogEventArgs e);
+
+		/// <summary>
+		/// Chains the provided target to the current target, so that when this
+		/// target receives an event, the provided target is also executed.
+		/// </summary>
+		/// <param name="target">The target to chain with the current one.</param>
+		/// <remarks>Chaining a target multiple times will cause the target to
+		/// be invoked multiple times for every event.</remarks>
+		void Chain(ILogTarget target);
+
+		/// <summary>
+		/// Unchains the provided target from the current target, so that the
+		/// provided target is no longer invoked when this target receives an event.
+		/// </summary>
+		/// <param name="target">The target to unchain</param>
+		/// <remarks>Multiply-chained targets need to be unchained the same amount
+		/// of time to be completely removed.</remarks>
+		void Unchain(ILogTarget target);
+	}
+
+	/// <summary>
+	/// Registers a provided log target to receive log messages for the lifespan
+	/// of this object.
+	/// </summary>
+	public sealed class LogSession : IDisposable
+	{
+		/// <summary>
+		/// Constructor. Registers the given log target with the provided threads
+		/// for listening for log messages.
+		/// </summary>
+		/// <param name="target">The target that should receive events.</param>
+		/// <param name="threads">The threads which the target will be registered
+		/// with for event notifications.</param>
+		public LogSession(ILogTarget target, params Thread[] threads)
+		{
+			Target = target;
+			Threads = threads.Distinct().ToArray();
+
+			foreach (Thread thread in Threads)
+				Logger.Listeners[thread].Add(target);
+		}
+
+		/// <summary>
+		/// Constructor. Registered the given log target with the current thread
+		/// for listening for log messages.
+		/// </summary>
+		/// <param name="target">The target which should receive events</param>
+		public LogSession(ILogTarget target)
+			: this(target, Thread.CurrentThread)
+		{
+		}
+
+		#region IDisposable Members
+
+		~LogSession()
+		{
+			Dispose(false);
+		}
+
+		private void Dispose(bool disposing)
+		{
+			if (Threads == null || Target == null)
+				return;
+
+			if (disposing)
+			{
+				//Disconnect the event handler from the threads.
+				foreach (Thread thread in Threads)
+					Logger.Listeners[thread].Remove(Target);
+			}
+
+			Threads = null;
+			Target = null;
+		}
+
+		public void Dispose()
+		{
+			Dispose(true);
+			GC.SuppressFinalize(this);
+		}
+
+		#endregion
+
+		/// <summary>
+		/// The target that should receive events. If this is null, the object
+		/// has been disposed.
+		/// </summary>
+		private ILogTarget Target;
+
+		/// <summary>
+		/// The list of threads which the target will be registered with for event
+		/// notifications. If this is null, the object is disposd.
+		/// </summary>
+		private Thread[] Threads;
+	}
+
+	/// <summary>
+	/// Collects a list of log entries into one session.
+	/// </summary>
+	/// <remarks>Instance functions of this class are thread-safe.</remarks>
+	public class LogSink : ILogTarget, IList<LogEntry>
+	{
+		#region ILoggerTarget Members
+
+		public void OnEventLogged(object sender, LogEventArgs e)
+		{
+			lock (List)
+				List.Add(e.LogEntry);
+
+			lock (ChainedTargets)
+				ChainedTargets.ForEach(target => target.OnEventLogged(sender, e));
+		}
+
+		public void Chain(ILogTarget target)
+		{
+			lock (ChainedTargets)
+				ChainedTargets.Add(target);
+		}
+
+		public void Unchain(ILogTarget target)
+		{
+			lock (ChainedTargets)
+				ChainedTargets.Remove(target);
+		}
+
+		/// <summary>
+		/// The list of targets which are chained to this one.
+		/// </summary>
+		private List<ILogTarget> ChainedTargets = new List<ILogTarget>();
+
+		#endregion
+
+		#region IList<LogEntry> Members
+
+		public int IndexOf(LogEntry item)
+		{
+			lock (List)
+				return IndexOf(item);
+		}
+
+		public void Insert(int index, LogEntry item)
+		{
+			lock (List)
+				List.Insert(index, item);
+		}
+
+		public void RemoveAt(int index)
+		{
+			lock (List)
+				List.RemoveAt(index);
+		}
+
+		public LogEntry this[int index]
+		{
+			get
+			{
+				lock (List)
+					return List[index];
+			}
+			set
+			{
+				lock (List)
+					List[index] = value;
+			}
+		}
+
+		#endregion
+
+		#region ICollection<LogEntry> Members
+
+		public void Add(LogEntry item)
+		{
+			lock (List)
+				List.Add(item);
+		}
+
+		public void Clear()
+		{
+			lock (List)
+				List.Clear();
+		}
+
+		public bool Contains(LogEntry item)
+		{
+			lock (List)
+				return List.Contains(item);
+		}
+
+		public void CopyTo(LogEntry[] array, int arrayIndex)
+		{
+			lock (List)
+				List.CopyTo(array, arrayIndex);
+		}
+
+		public int Count
+		{
+			get
+			{
+				lock(List)
+					return Count;
+			}
+		}
+
+		public bool IsReadOnly
+		{
+			get { return true; }
+		}
+
+		public bool Remove(LogEntry item)
+		{
+			lock (List)
+				return List.Remove(item);
+		}
+
+		#endregion
+
+		#region IEnumerable<LogEntry> Members
+
+		public IEnumerator<LogEntry> GetEnumerator()
+		{
+			return List.GetEnumerator();
+		}
+
+		#endregion
+
+		#region IEnumerable Members
+
+		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+		{
+			return List.GetEnumerator();
+		}
+
+		#endregion
+
+		/// <summary>
+		/// Gets the highest log level in the current log sink.
+		/// </summary>
+		public LogLevel Highest
+		{
+			get
+			{
+				lock (List)
+					return List.Max(delegate(LogEntry e) { return e.Level; });
+			}
+		}
+
+		/// <summary>
+		/// Gets the time the first message was logged.
+		/// </summary>
+		public DateTime StartTime
+		{
+			get
+			{
+				lock (List)
+					return List.First().Timestamp;
+			}
+		}
+
+		/// <summary>
+		/// Gets the time the last message was logged.
+		/// </summary>
+		public DateTime EndTime
+		{
+			get
+			{
+				lock (List)
+					return List.Last().Timestamp;
+			}
+		}
+
+		/// <summary>
+		/// The backing store of this session.
+		/// </summary>
+		private List<LogEntry> List = new List<LogEntry>();
+	}
+}
Index: /branches/eraser6/CodeReview/Eraser.Util/Eraser.Util.csproj
===================================================================
--- /branches/eraser6/CodeReview/Eraser.Util/Eraser.Util.csproj	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser.Util/Eraser.Util.csproj	(revision 1777)
@@ -50,4 +50,8 @@
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="CommonLibrary, Version=0.9.3.10, Culture=neutral, PublicKeyToken=3ac89a0351e689b6, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\Dependencies\CommonLibrary.dll</HintPath>
+    </Reference>
     <Reference Include="ICSharpCode.SharpZipLib, Version=0.85.5.452, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
@@ -102,4 +106,5 @@
     <Compile Include="ConsoleWindow.cs" />
     <Compile Include="FileSize.cs" />
+    <Compile Include="Logger.cs" />
     <Compile Include="NativeMethods\DbgHelp.cs" />
     <Compile Include="NativeMethods\Mpr.cs" />
Index: /branches/eraser6/CodeReview/Eraser.Util/Theming.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser.Util/Theming.cs	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser.Util/Theming.cs	(revision 1777)
@@ -188,5 +188,5 @@
 		/// </summary>
 		private static Dictionary<ToolStrip, UXThemeMenuRenderer> ThemedMenus =
-			new Dictionary<ToolStrip,UXThemeMenuRenderer>();
+			new Dictionary<ToolStrip, UXThemeMenuRenderer>();
 
 		/// <summary>
Index: /branches/eraser6/CodeReview/Eraser.DefaultPlugins/FileSystems/Windows.cs
===================================================================
--- /branches/eraser6/CodeReview/Eraser.DefaultPlugins/FileSystems/Windows.cs	(revision 1776)
+++ /branches/eraser6/CodeReview/Eraser.DefaultPlugins/FileSystems/Windows.cs	(revision 1777)
@@ -155,6 +155,5 @@
 
 		public override void EraseClusterTips(VolumeInfo info, ErasureMethod method,
-			Logger log, ClusterTipsSearchProgress searchCallback,
-			ClusterTipsEraseProgress eraseCallback)
+			ClusterTipsSearchProgress searchCallback, ClusterTipsEraseProgress eraseCallback)
 		{
 			//List all the files which can be erased.
@@ -163,5 +162,5 @@
 				throw new InvalidOperationException(S._("Could not erase cluster tips in {0} " +
 					"as the volume is not mounted.", info.VolumeId));
-			ListFiles(new DirectoryInfo(info.MountPoints[0]), files, log, searchCallback);
+			ListFiles(new DirectoryInfo(info.MountPoints[0]), files, searchCallback);
 
 			//For every file, erase the cluster tips.
@@ -180,13 +179,12 @@
 				catch (UnauthorizedAccessException)
 				{
-					log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have its " +
-						"cluster tips erased because you do not have the required permissions to " +
-						"erase the file cluster tips.", files[i]), LogLevel.Information));
+					Logger.Log(S._("{0} did not have its cluster tips erased because you do not " +
+						"have the required permissions to erase the file cluster tips.", files[i]),
+						LogLevel.Information);
 				}
 				catch (IOException e)
 				{
-					log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have its " +
-						"cluster tips erased. The error returned was: {1}", files[i],
-						e.Message), LogLevel.Error));
+					Logger.Log(S._("{0} did not have its cluster tips erased. The error returned " +
+						"was: {1}", files[i], e.Message), LogLevel.Error);
 				}
 				finally
@@ -194,9 +192,10 @@
 					streamInfo.Attributes = fileAttr;
 				}
+
 				eraseCallback(i, files.Count, files[i]);
 			}
 		}
 
-		private void ListFiles(DirectoryInfo info, List<string> files, Logger log,
+		private void ListFiles(DirectoryInfo info, List<string> files,
 			ClusterTipsSearchProgress searchCallback)
 		{
@@ -206,7 +205,7 @@
 				if ((info.Attributes & FileAttributes.ReparsePoint) != 0)
 				{
-					log.LastSessionEntries.Add(new LogEntry(S._("Files in {0} did " +
-						"not have their cluster tips erased because it is a hard link or " +
-						"a symbolic link.", info.FullName), LogLevel.Information));
+					Logger.Log(S._("Files in {0} did not have their cluster tips erased because " +
+						"it is a hard link or a symbolic link.", info.FullName),
+						LogLevel.Information);
 					return;
 				}
@@ -214,18 +213,16 @@
 				foreach (FileInfo file in info.GetFiles())
 					if (file.IsProtectedSystemFile())
-						log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have " +
-							"its cluster tips erased, because it is a system file",
-							file.FullName), LogLevel.Information));
+						Logger.Log(S._("{0} did not have its cluster tips erased, because it is " +
+							"a system file", file.FullName), LogLevel.Information);
 					else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
-						log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have " +
-							"its cluster tips erased because it is a hard link or a " +
-							"symbolic link.", file.FullName), LogLevel.Information));
+						Logger.Log(S._("{0} did not have its cluster tips erased because it is a " +
+							"hard link or a symbolic link.", file.FullName), LogLevel.Information);
 					else if ((file.Attributes & FileAttributes.Compressed) != 0 ||
 						(file.Attributes & FileAttributes.Encrypted) != 0 ||
 						(file.Attributes & FileAttributes.SparseFile) != 0)
 					{
-						log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have " +
-							"its cluster tips erased because it is compressed, encrypted " +
-							"or a sparse file.", file.FullName), LogLevel.Information));
+						Logger.Log(S._("{0} did not have its cluster tips erased because it is " +
+							"compressed, encrypted or a sparse file.", file.FullName),
+							LogLevel.Information);
 					}
 					else
@@ -240,13 +237,13 @@
 						catch (UnauthorizedAccessException e)
 						{
-							log.LastSessionEntries.Add(new LogEntry(S._("{0} did not " +
-								"have its cluster tips erased because of the following " +
-								"error: {1}", info.FullName, e.Message), LogLevel.Error));
+							Logger.Log(S._("{0} did not have its cluster tips erased because of " +
+								"the following error: {1}", info.FullName, e.Message),
+								LogLevel.Error);
 						}
 						catch (IOException e)
 						{
-							log.LastSessionEntries.Add(new LogEntry(S._("{0} did not " +
-								"have its cluster tips erased because of the following " +
-								"error: {1}", info.FullName, e.Message), LogLevel.Error));
+							Logger.Log(S._("{0} did not have its cluster tips erased because of " +
+								"the following error: {1}", info.FullName, e.Message),
+								LogLevel.Error);
 						}
 					}
@@ -255,18 +252,16 @@
 				{
 					searchCallback(subDirInfo.FullName);
-					ListFiles(subDirInfo, files, log, searchCallback);
+					ListFiles(subDirInfo, files, searchCallback);
 				}
 			}
 			catch (UnauthorizedAccessException e)
 			{
-				log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have its " +
-					"cluster tips erased because of the following error: {1}",
-					info.FullName, e.Message), LogLevel.Error));
+				Logger.Log(S._("{0} did not have its cluster tips erased because of the " +
+					"following error: {1}", info.FullName, e.Message), LogLevel.Error);
 			}
 			catch (IOException e)
 			{
-				log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have its " +
-					"cluster tips erased because of the following error: {1}",
-					info.FullName, e.Message), LogLevel.Error));
+				Logger.Log(S._("{0} did not have its cluster tips erased because of the " +
+					"following error: {1}", info.FullName, e.Message), LogLevel.Error);
 			}
 		}
