source: branches/eraser6/XmlTaskLists/Eraser.Util/Logger.cs @ 2602

Revision 2602, 19.2 KB checked in by lowjoel, 2 years ago (diff)

Make the LogSink? a base class (LogSinkBase?) and implement a normal LogSink? (API compatible with the old LogSink?) and a new LazyLogSink? class which will load a log from disk as required.
As a result of this change, Task will have two WriteXml? functions: The default will continue to generate the same code; the new overload will place references to logs if the logs are larger than a certain threshold. This is to optimize the task list for size when loading/unloading.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Linq;
24using System.Collections.Generic;
25using System.Text;
26
27using System.Collections.ObjectModel;
28using System.Threading;
29using System.Runtime.Serialization;
30using System.Xml;
31using System.Xml.Serialization;
32using System.Security.Permissions;
33using System.IO;
34
35namespace Eraser.Util
36{
37    /// <summary>
38    /// The levels of logging allowing for the filtering of messages.
39    /// </summary>
40    public enum LogLevel
41    {
42        /// <summary>
43        /// Informative messages.
44        /// </summary>
45        Information,
46
47        /// <summary>
48        /// Notice messages.
49        /// </summary>
50        Notice,
51
52        /// <summary>
53        /// Warning messages.
54        /// </summary>
55        Warning,
56
57        /// <summary>
58        /// Error messages.
59        /// </summary>
60        Error,
61
62        /// <summary>
63        /// Fatal errors.
64        /// </summary>
65        Fatal
66    }
67
68    /// <summary>
69    /// Represents a log entry.
70    /// </summary>
71    [Serializable]
72    public struct LogEntry : ISerializable, IXmlSerializable
73    {
74        #region Serialization code
75        private LogEntry(SerializationInfo info, StreamingContext context)
76            : this()
77        {
78            Level = (LogLevel)info.GetValue("Level", typeof(LogLevel));
79            Timestamp = (DateTime)info.GetValue("Timestamp", typeof(DateTime));
80            Message = (string)info.GetValue("Message", typeof(string));
81        }
82
83        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
84        public void GetObjectData(SerializationInfo info, StreamingContext context)
85        {
86            info.AddValue("Level", Level);
87            info.AddValue("Timestamp", Timestamp);
88            info.AddValue("Message", Message);
89        }
90
91        public System.Xml.Schema.XmlSchema GetSchema()
92        {
93            return null;
94        }
95
96        public void ReadXml(XmlReader reader)
97        {
98            LogLevel level;
99            DateTime timestamp;
100            if (!Enum.TryParse<LogLevel>(reader.GetAttribute("level"), out level))
101                throw new InvalidDataException();
102            if (!DateTime.TryParse(reader.GetAttribute("timestamp"), out timestamp))
103                throw new InvalidDataException();
104
105            Level = level;
106            Timestamp = timestamp;
107            Message = reader.ReadString();
108            reader.Read();
109        }
110
111        public void WriteXml(XmlWriter writer)
112        {
113            writer.WriteAttributeString("level", Level.ToString());
114            writer.WriteAttributeString("timestamp", Timestamp.ToString("O"));
115            writer.WriteString(Message);
116        }
117        #endregion
118
119        /// <summary>
120        /// Creates a LogEntry structure.
121        /// </summary>
122        /// <param name="message">The log message.</param>
123        /// <param name="level">The type of log entry.</param>
124        public LogEntry(string message, LogLevel level)
125            : this()
126        {
127            Message = message;
128            Level = level;
129            Timestamp = DateTime.Now;
130        }
131
132        /// <summary>
133        /// The type of log entry.
134        /// </summary>
135        public LogLevel Level { get; private set; }
136
137        /// <summary>
138        /// The time which the message was logged.
139        /// </summary>
140        public DateTime Timestamp { get; private set; }
141
142        /// <summary>
143        /// The log message.
144        /// </summary>
145        public string Message { get; private set; }
146    }
147
148    /// <summary>
149    /// Event Data for all Logger events.
150    /// </summary>
151    public class LogEventArgs : EventArgs
152    {
153        /// <summary>
154        /// Constructor.
155        /// </summary>
156        /// <param name="entry">The log entry that was just logged.</param>
157        public LogEventArgs(LogEntry entry)
158        {
159            LogEntry = entry;
160        }
161
162        /// <summary>
163        /// The log entry that was just logged.
164        /// </summary>
165        public LogEntry LogEntry { get; private set; }
166    }
167
168    /// <summary>
169    /// Provides a standard logging interface to the rest of the Eraser classes.
170    /// </summary>
171    public static class Logger
172    {
173        static Logger()
174        {
175            Listeners = new LogThreadDictionary();
176        }
177
178        /// <summary>
179        /// Logs an informational message.
180        /// </summary>
181        /// <param name="message">The message to log.</param>
182        public static void Log(string message)
183        {
184            Log(new LogEntry(message, LogLevel.Information));
185        }
186
187        /// <summary>
188        /// Logs a message to the logger.
189        /// </summary>
190        /// <param name="message">The message to store.</param>
191        /// <param name="level">The level of the message.</param>
192        public static void Log(string message, LogLevel level)
193        {
194            Log(new LogEntry(message, level));
195        }
196
197        /// <summary>
198        /// Logs the provided entry to the logger.
199        /// </summary>
200        /// <param name="entry">The log entry to store.</param>
201        public static void Log(LogEntry entry)
202        {
203            Thread currentThread = Thread.CurrentThread;
204            List<ILogTarget> targets = new List<ILogTarget>();
205
206            if (Listeners.ContainsKey(currentThread))
207            {
208                LogThreadTargets threadTargets = Listeners[currentThread];
209                if (threadTargets != null)
210                    targets.AddRange(threadTargets);
211            }
212
213            targets.ForEach(
214                target => target.OnEventLogged(currentThread, new LogEventArgs(entry)));
215        }
216
217        /// <summary>
218        /// The list of listeners for events on a particular thread.
219        /// </summary>
220        public static LogThreadDictionary Listeners { get; private set; }
221    }
222
223    /// <summary>
224    /// The Logger Thread Dictionary, which maps log event listeners to threads.
225    /// This mainly serves as a thread-safe Dictionary.
226    /// </summary>
227    public class LogThreadDictionary : IDictionary<Thread, LogThreadTargets>
228    {
229        #region IDictionary<Thread,LogThreadTargets> Members
230
231        public void Add(Thread key, LogThreadTargets value)
232        {
233            lock (Dictionary)
234                Dictionary.Add(key, value);
235        }
236
237        public bool ContainsKey(Thread key)
238        {
239            return Dictionary.ContainsKey(key);
240        }
241
242        public ICollection<Thread> Keys
243        {
244            get
245            {
246                lock (Dictionary)
247                {
248                    Thread[] result = new Thread[Dictionary.Keys.Count];
249                    Dictionary.Keys.CopyTo(result, 0);
250
251                    return new ReadOnlyCollection<Thread>(result);
252                }
253            }
254        }
255
256        public bool Remove(Thread key)
257        {
258            lock (Dictionary)
259                return Dictionary.Remove(key);
260        }
261
262        public bool TryGetValue(Thread key, out LogThreadTargets value)
263        {
264            lock (Dictionary)
265                return Dictionary.TryGetValue(key, out value);
266        }
267
268        public ICollection<LogThreadTargets> Values
269        {
270            get
271            {
272                lock (Dictionary)
273                {
274                    LogThreadTargets[] result =
275                        new LogThreadTargets[Dictionary.Values.Count];
276                    Dictionary.Values.CopyTo(result, 0);
277
278                    return new ReadOnlyCollection<LogThreadTargets>(result);
279                }
280            }
281        }
282
283        public LogThreadTargets this[Thread key]
284        {
285            get
286            {
287                lock (Dictionary)
288                    return Dictionary[key];
289            }
290            set
291            {
292                lock (Dictionary)
293                    Dictionary[key] = value;
294            }
295        }
296
297        #endregion
298
299        #region ICollection<KeyValuePair<Thread,LogThreadTargets>> Members
300
301        public void Add(KeyValuePair<Thread, LogThreadTargets> item)
302        {
303            lock (Dictionary)
304                Dictionary.Add(item.Key, item.Value);
305        }
306
307        public void Clear()
308        {
309            lock (Dictionary)
310                Dictionary.Clear();
311        }
312
313        public bool Contains(KeyValuePair<Thread, LogThreadTargets> item)
314        {
315            lock (Dictionary)
316                return Dictionary.ContainsKey(item.Key) && Dictionary[item.Key] == item.Value;
317        }
318
319        public void CopyTo(KeyValuePair<Thread, LogThreadTargets>[] array, int arrayIndex)
320        {
321            throw new NotImplementedException();
322        }
323
324        public int Count
325        {
326            get
327            {
328                lock (Dictionary)
329                    return Dictionary.Count;
330            }
331        }
332
333        public bool IsReadOnly
334        {
335            get { return false; }
336        }
337
338        public bool Remove(KeyValuePair<Thread, LogThreadTargets> item)
339        {
340            lock (Dictionary)
341                return Dictionary.Remove(item.Key);
342        }
343
344        #endregion
345
346        #region IEnumerable<KeyValuePair<Thread,LogThreadTargets>> Members
347
348        public IEnumerator<KeyValuePair<Thread, LogThreadTargets>> GetEnumerator()
349        {
350            return Dictionary.GetEnumerator();
351        }
352
353        #endregion
354
355        #region IEnumerable Members
356
357        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
358        {
359            return Dictionary.GetEnumerator();
360        }
361
362        #endregion
363
364        /// <summary>
365        /// The backing store for this dictionary.
366        /// </summary>
367        private Dictionary<Thread, LogThreadTargets> Dictionary =
368            new Dictionary<Thread, LogThreadTargets>();
369    }
370
371    public class LogThreadTargets : IList<ILogTarget>
372    {
373        #region IList<ILogTarget> Members
374
375        public int IndexOf(ILogTarget item)
376        {
377            lock (List)
378                return List.IndexOf(item);
379        }
380
381        public void Insert(int index, ILogTarget item)
382        {
383            lock (List)
384                List.Insert(index, item);
385        }
386
387        public void RemoveAt(int index)
388        {
389            lock (List)
390                List.RemoveAt(index);
391        }
392
393        public ILogTarget this[int index]
394        {
395            get
396            {
397                lock (List)
398                    return List[index];
399            }
400            set
401            {
402                lock (List)
403                    List[index] = value;
404            }
405        }
406
407        #endregion
408
409        #region ICollection<ILogTarget> Members
410
411        public void Add(ILogTarget item)
412        {
413            lock (List)
414                List.Add(item);
415        }
416
417        public void Clear()
418        {
419            lock (List)
420                List.Clear();
421        }
422
423        public bool Contains(ILogTarget item)
424        {
425            lock (List)
426                return List.Contains(item);
427        }
428
429        public void CopyTo(ILogTarget[] array, int arrayIndex)
430        {
431            lock (List)
432                List.CopyTo(array, arrayIndex);
433        }
434
435        public int Count
436        {
437            get
438            {
439                lock (List)
440                    return List.Count;
441            }
442        }
443
444        public bool IsReadOnly
445        {
446            get { return false; }
447        }
448
449        public bool Remove(ILogTarget item)
450        {
451            lock (List)
452                return List.Remove(item);
453        }
454
455        #endregion
456
457        #region IEnumerable<ILogTarget> Members
458
459        public IEnumerator<ILogTarget> GetEnumerator()
460        {
461            return List.GetEnumerator();
462        }
463
464        #endregion
465
466        #region IEnumerable Members
467
468        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
469        {
470            return List.GetEnumerator();
471        }
472
473        #endregion
474
475        /// <summary>
476        /// The backing store for this list.
477        /// </summary>
478        private List<ILogTarget> List = new List<ILogTarget>();
479    }
480
481    /// <summary>
482    /// The logger target interface which all interested listeners of log events must
483    /// implement.
484    /// </summary>
485    public interface ILogTarget
486    {
487        /// <summary>
488        /// The handler for events.
489        /// </summary>
490        /// <param name="sender">The sender of the event.</param>
491        /// <param name="e">The event data associated with the event.</param>
492        void OnEventLogged(object sender, LogEventArgs e);
493
494        /// <summary>
495        /// Chains the provided target to the current target, so that when this
496        /// target receives an event, the provided target is also executed.
497        /// </summary>
498        /// <param name="target">The target to chain with the current one.</param>
499        /// <remarks>Chaining a target multiple times will cause the target to
500        /// be invoked multiple times for every event.</remarks>
501        void Chain(ILogTarget target);
502
503        /// <summary>
504        /// Unchains the provided target from the current target, so that the
505        /// provided target is no longer invoked when this target receives an event.
506        /// </summary>
507        /// <param name="target">The target to unchain</param>
508        /// <remarks>Multiply-chained targets need to be unchained the same amount
509        /// of time to be completely removed.</remarks>
510        void Unchain(ILogTarget target);
511    }
512
513    /// <summary>
514    /// Registers a provided log target to receive log messages for the lifespan
515    /// of this object.
516    /// </summary>
517    public sealed class LogSession : IDisposable
518    {
519        /// <summary>
520        /// Constructor. Registers the given log target with the provided threads
521        /// for listening for log messages.
522        /// </summary>
523        /// <param name="target">The target that should receive events.</param>
524        /// <param name="threads">The threads which the target will be registered
525        /// with for event notifications.</param>
526        public LogSession(ILogTarget target, params Thread[] threads)
527        {
528            Target = target;
529            Threads = threads.Distinct().ToArray();
530
531            foreach (Thread thread in Threads)
532            {
533                if (!Logger.Listeners.ContainsKey(thread))
534                    Logger.Listeners.Add(thread, new LogThreadTargets());
535                Logger.Listeners[thread].Add(target);
536            }
537
538            Target.OnEventLogged(this, new LogEventArgs(
539                new LogEntry(S._("Session started"), LogLevel.Information)));
540        }
541
542        /// <summary>
543        /// Constructor. Registered the given log target with the current thread
544        /// for listening for log messages.
545        /// </summary>
546        /// <param name="target">The target which should receive events</param>
547        public LogSession(ILogTarget target)
548            : this(target, Thread.CurrentThread)
549        {
550        }
551
552        #region IDisposable Members
553
554        ~LogSession()
555        {
556            Dispose(false);
557        }
558
559        private void Dispose(bool disposing)
560        {
561            if (Threads == null || Target == null)
562                return;
563
564            if (disposing)
565            {
566                //Disconnect the event handler from the threads.
567                foreach (Thread thread in Threads)
568                    Logger.Listeners[thread].Remove(Target);
569            }
570
571            Target.OnEventLogged(this, new LogEventArgs(
572                new LogEntry(S._("Session ended"), LogLevel.Information)));
573            Threads = null;
574            Target = null;
575        }
576
577        public void Dispose()
578        {
579            Dispose(true);
580            GC.SuppressFinalize(this);
581        }
582
583        #endregion
584
585        /// <summary>
586        /// The target that should receive events. If this is null, the object
587        /// has been disposed.
588        /// </summary>
589        private ILogTarget Target;
590
591        /// <summary>
592        /// The list of threads which the target will be registered with for event
593        /// notifications. If this is null, the object is disposd.
594        /// </summary>
595        private Thread[] Threads;
596    }
597
598    /// <summary>
599    /// Collects a list of log entries into one session.
600    /// </summary>
601    /// <remarks>Instance functions of this class are thread-safe.</remarks>
602    [Serializable]
603    public abstract class LogSinkBase : ISerializable, ILogTarget, IList<LogEntry>
604    {
605        public LogSinkBase()
606        {
607        }
608
609        #region ISerializable Members
610
611        public LogSinkBase(SerializationInfo info, StreamingContext context)
612        {
613        }
614
615        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
616        public abstract void GetObjectData(SerializationInfo info, StreamingContext context);
617
618        #endregion
619
620        #region ILoggerTarget Members
621
622        public void OnEventLogged(object sender, LogEventArgs e)
623        {
624            lock (List)
625                List.Add(e.LogEntry);
626
627            lock (ChainedTargets)
628                ChainedTargets.ForEach(target => target.OnEventLogged(sender, e));
629        }
630
631        public void Chain(ILogTarget target)
632        {
633            lock (ChainedTargets)
634                ChainedTargets.Add(target);
635        }
636
637        public void Unchain(ILogTarget target)
638        {
639            lock (ChainedTargets)
640                ChainedTargets.Remove(target);
641        }
642
643        /// <summary>
644        /// The list of targets which are chained to this one.
645        /// </summary>
646        private List<ILogTarget> ChainedTargets = new List<ILogTarget>();
647
648        #endregion
649
650        #region IList<LogEntry> Members
651
652        public int IndexOf(LogEntry item)
653        {
654            lock (List)
655                return IndexOf(item);
656        }
657
658        public void Insert(int index, LogEntry item)
659        {
660            lock (List)
661                List.Insert(index, item);
662        }
663
664        public void RemoveAt(int index)
665        {
666            lock (List)
667                List.RemoveAt(index);
668        }
669
670        public LogEntry this[int index]
671        {
672            get
673            {
674                lock (List)
675                    return List[index];
676            }
677            set
678            {
679                lock (List)
680                    List[index] = value;
681            }
682        }
683
684        #endregion
685
686        #region ICollection<LogEntry> Members
687
688        public void Add(LogEntry item)
689        {
690            lock (List)
691                List.Add(item);
692        }
693
694        public void Clear()
695        {
696            lock (List)
697                List.Clear();
698        }
699
700        public bool Contains(LogEntry item)
701        {
702            lock (List)
703                return List.Contains(item);
704        }
705
706        public void CopyTo(LogEntry[] array, int arrayIndex)
707        {
708            lock (List)
709                List.CopyTo(array, arrayIndex);
710        }
711
712        public int Count
713        {
714            get
715            {
716                lock(List)
717                    return Count;
718            }
719        }
720
721        public bool IsReadOnly
722        {
723            get { return true; }
724        }
725
726        public bool Remove(LogEntry item)
727        {
728            lock (List)
729                return List.Remove(item);
730        }
731
732        #endregion
733
734        #region IEnumerable<LogEntry> Members
735
736        public IEnumerator<LogEntry> GetEnumerator()
737        {
738            return List.GetEnumerator();
739        }
740
741        #endregion
742
743        #region IEnumerable Members
744
745        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
746        {
747            return List.GetEnumerator();
748        }
749
750        #endregion
751
752        /// <summary>
753        /// Gets the highest log level in the current log sink.
754        /// </summary>
755        public LogLevel Highest
756        {
757            get
758            {
759                lock (List)
760                    return List.Max(delegate(LogEntry e) { return e.Level; });
761            }
762        }
763
764        /// <summary>
765        /// Gets the time the first message was logged.
766        /// </summary>
767        public DateTime StartTime
768        {
769            get
770            {
771                lock (List)
772                    return List.First().Timestamp;
773            }
774        }
775
776        /// <summary>
777        /// Gets the time the last message was logged.
778        /// </summary>
779        public DateTime EndTime
780        {
781            get
782            {
783                lock (List)
784                    return List.Last().Timestamp;
785            }
786        }
787
788        /// <summary>
789        /// Saves the log to the given path in an XML format which can be read
790        /// by <see cref="LazyLogSink"/>.
791        /// </summary>
792        /// <param name="path">The path to save the log contents to.</param>
793        public void Save(string path)
794        {
795            using (FileStream stream = new FileStream(path, FileMode.OpenOrCreate))
796                Save(stream);
797        }
798
799        public void Save(Stream stream)
800        {
801            IList<LogEntry> list = List;
802            lock (list)
803            {
804                XmlRootAttribute root = new XmlRootAttribute("Log");
805                XmlSerializer serializer = new XmlSerializer(list.GetType(), root);
806
807                serializer.Serialize(stream, list);
808            }
809        }
810
811        /// <summary>
812        /// The backing store of this session.
813        /// </summary>
814        protected abstract IList<LogEntry> List
815        {
816            get;
817        }
818    }
819
820    [Serializable]
821    public class LogSink : LogSinkBase
822    {
823        public LogSink()
824        {
825            list = new List<LogEntry>();
826        }
827
828        #region ISerializable Members
829
830        public LogSink(SerializationInfo info, StreamingContext context)
831            : base(info, context)
832        {
833            list = (List<LogEntry>)info.GetValue("List", typeof(List<LogEntry>));
834        }
835
836        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
837        public override void GetObjectData(SerializationInfo info, StreamingContext context)
838        {
839            info.AddValue("List", List);
840        }
841
842        #endregion
843
844        protected override IList<LogEntry> List
845        {
846            get { return list; }
847        }
848        private List<LogEntry> list;
849    }
850
851    [Serializable]
852    public class LazyLogSink : LogSinkBase
853    {
854        /// <summary>
855        /// Constructor.
856        /// </summary>
857        /// <param name="path">The path to the log file.</param>
858        public LazyLogSink(string path)
859        {
860            SavePath = path;
861        }
862
863        #region ISerializable Members
864
865        public LazyLogSink(SerializationInfo info, StreamingContext context)
866            : base(info, context)
867        {
868            SavePath = (string)info.GetValue("SavePath", typeof(string));
869        }
870
871        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
872        public override void GetObjectData(SerializationInfo info, StreamingContext context)
873        {
874            info.AddValue("SavePath", SavePath);
875        }
876
877        #endregion
878
879        private void LoadList()
880        {
881            lock (Synchronise)
882            {
883                XmlRootAttribute root = new XmlRootAttribute("Log");
884                XmlSerializer serializer = new XmlSerializer(typeof(List<LogEntry>), root);
885
886                using (FileStream stream = new FileStream(SavePath, FileMode.Open))
887                    list = (List<LogEntry>)serializer.Deserialize(stream);
888            }
889        }
890
891        /// <summary>
892        /// The path the log was saved to.
893        /// </summary>
894        public string SavePath
895        {
896            get;
897            private set;
898        }
899
900        protected override IList<LogEntry> List
901        {
902            get
903            {
904                if (list == null)
905                    LoadList();
906                return list;
907            }
908        }
909
910        private List<LogEntry> list;
911
912        /// <summary>
913        /// Private object to makw sure LoadList is not called on multiple threads.
914        /// </summary>
915        private object Synchronise = new object();
916    }
917}
Note: See TracBrowser for help on using the repository browser.