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

Revision 2593, 16.6 KB checked in by lowjoel, 2 years ago (diff)

Allow log entries to be serialized to XML too.

  • 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.Value;
108        }
109
110        public void WriteXml(XmlWriter writer)
111        {
112            writer.WriteAttributeString("level", Level.ToString());
113            writer.WriteAttributeString("timestamp", Timestamp.ToString());
114            writer.WriteString(Message);
115        }
116        #endregion
117
118        /// <summary>
119        /// Creates a LogEntry structure.
120        /// </summary>
121        /// <param name="message">The log message.</param>
122        /// <param name="level">The type of log entry.</param>
123        public LogEntry(string message, LogLevel level)
124            : this()
125        {
126            Message = message;
127            Level = level;
128            Timestamp = DateTime.Now;
129        }
130
131        /// <summary>
132        /// The type of log entry.
133        /// </summary>
134        public LogLevel Level { get; private set; }
135
136        /// <summary>
137        /// The time which the message was logged.
138        /// </summary>
139        public DateTime Timestamp { get; private set; }
140
141        /// <summary>
142        /// The log message.
143        /// </summary>
144        public string Message { get; private set; }
145    }
146
147    /// <summary>
148    /// Event Data for all Logger events.
149    /// </summary>
150    public class LogEventArgs : EventArgs
151    {
152        /// <summary>
153        /// Constructor.
154        /// </summary>
155        /// <param name="entry">The log entry that was just logged.</param>
156        public LogEventArgs(LogEntry entry)
157        {
158            LogEntry = entry;
159        }
160
161        /// <summary>
162        /// The log entry that was just logged.
163        /// </summary>
164        public LogEntry LogEntry { get; private set; }
165    }
166
167    /// <summary>
168    /// Provides a standard logging interface to the rest of the Eraser classes.
169    /// </summary>
170    public static class Logger
171    {
172        static Logger()
173        {
174            Listeners = new LogThreadDictionary();
175        }
176
177        /// <summary>
178        /// Logs an informational message.
179        /// </summary>
180        /// <param name="message">The message to log.</param>
181        public static void Log(string message)
182        {
183            Log(new LogEntry(message, LogLevel.Information));
184        }
185
186        /// <summary>
187        /// Logs a message to the logger.
188        /// </summary>
189        /// <param name="message">The message to store.</param>
190        /// <param name="level">The level of the message.</param>
191        public static void Log(string message, LogLevel level)
192        {
193            Log(new LogEntry(message, level));
194        }
195
196        /// <summary>
197        /// Logs the provided entry to the logger.
198        /// </summary>
199        /// <param name="entry">The log entry to store.</param>
200        public static void Log(LogEntry entry)
201        {
202            Thread currentThread = Thread.CurrentThread;
203            List<ILogTarget> targets = new List<ILogTarget>();
204
205            if (Listeners.ContainsKey(currentThread))
206            {
207                LogThreadTargets threadTargets = Listeners[currentThread];
208                if (threadTargets != null)
209                    targets.AddRange(threadTargets);
210            }
211
212            targets.ForEach(
213                target => target.OnEventLogged(currentThread, new LogEventArgs(entry)));
214        }
215
216        /// <summary>
217        /// The list of listeners for events on a particular thread.
218        /// </summary>
219        public static LogThreadDictionary Listeners { get; private set; }
220    }
221
222    /// <summary>
223    /// The Logger Thread Dictionary, which maps log event listeners to threads.
224    /// This mainly serves as a thread-safe Dictionary.
225    /// </summary>
226    public class LogThreadDictionary : IDictionary<Thread, LogThreadTargets>
227    {
228        #region IDictionary<Thread,LogThreadTargets> Members
229
230        public void Add(Thread key, LogThreadTargets value)
231        {
232            lock (Dictionary)
233                Dictionary.Add(key, value);
234        }
235
236        public bool ContainsKey(Thread key)
237        {
238            return Dictionary.ContainsKey(key);
239        }
240
241        public ICollection<Thread> Keys
242        {
243            get
244            {
245                lock (Dictionary)
246                {
247                    Thread[] result = new Thread[Dictionary.Keys.Count];
248                    Dictionary.Keys.CopyTo(result, 0);
249
250                    return new ReadOnlyCollection<Thread>(result);
251                }
252            }
253        }
254
255        public bool Remove(Thread key)
256        {
257            lock (Dictionary)
258                return Dictionary.Remove(key);
259        }
260
261        public bool TryGetValue(Thread key, out LogThreadTargets value)
262        {
263            lock (Dictionary)
264                return Dictionary.TryGetValue(key, out value);
265        }
266
267        public ICollection<LogThreadTargets> Values
268        {
269            get
270            {
271                lock (Dictionary)
272                {
273                    LogThreadTargets[] result =
274                        new LogThreadTargets[Dictionary.Values.Count];
275                    Dictionary.Values.CopyTo(result, 0);
276
277                    return new ReadOnlyCollection<LogThreadTargets>(result);
278                }
279            }
280        }
281
282        public LogThreadTargets this[Thread key]
283        {
284            get
285            {
286                lock (Dictionary)
287                    return Dictionary[key];
288            }
289            set
290            {
291                lock (Dictionary)
292                    Dictionary[key] = value;
293            }
294        }
295
296        #endregion
297
298        #region ICollection<KeyValuePair<Thread,LogThreadTargets>> Members
299
300        public void Add(KeyValuePair<Thread, LogThreadTargets> item)
301        {
302            lock (Dictionary)
303                Dictionary.Add(item.Key, item.Value);
304        }
305
306        public void Clear()
307        {
308            lock (Dictionary)
309                Dictionary.Clear();
310        }
311
312        public bool Contains(KeyValuePair<Thread, LogThreadTargets> item)
313        {
314            lock (Dictionary)
315                return Dictionary.ContainsKey(item.Key) && Dictionary[item.Key] == item.Value;
316        }
317
318        public void CopyTo(KeyValuePair<Thread, LogThreadTargets>[] array, int arrayIndex)
319        {
320            throw new NotImplementedException();
321        }
322
323        public int Count
324        {
325            get
326            {
327                lock (Dictionary)
328                    return Dictionary.Count;
329            }
330        }
331
332        public bool IsReadOnly
333        {
334            get { return false; }
335        }
336
337        public bool Remove(KeyValuePair<Thread, LogThreadTargets> item)
338        {
339            lock (Dictionary)
340                return Dictionary.Remove(item.Key);
341        }
342
343        #endregion
344
345        #region IEnumerable<KeyValuePair<Thread,LogThreadTargets>> Members
346
347        public IEnumerator<KeyValuePair<Thread, LogThreadTargets>> GetEnumerator()
348        {
349            return Dictionary.GetEnumerator();
350        }
351
352        #endregion
353
354        #region IEnumerable Members
355
356        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
357        {
358            return Dictionary.GetEnumerator();
359        }
360
361        #endregion
362
363        /// <summary>
364        /// The backing store for this dictionary.
365        /// </summary>
366        private Dictionary<Thread, LogThreadTargets> Dictionary =
367            new Dictionary<Thread, LogThreadTargets>();
368    }
369
370    public class LogThreadTargets : IList<ILogTarget>
371    {
372        #region IList<ILogTarget> Members
373
374        public int IndexOf(ILogTarget item)
375        {
376            lock (List)
377                return List.IndexOf(item);
378        }
379
380        public void Insert(int index, ILogTarget item)
381        {
382            lock (List)
383                List.Insert(index, item);
384        }
385
386        public void RemoveAt(int index)
387        {
388            lock (List)
389                List.RemoveAt(index);
390        }
391
392        public ILogTarget this[int index]
393        {
394            get
395            {
396                lock (List)
397                    return List[index];
398            }
399            set
400            {
401                lock (List)
402                    List[index] = value;
403            }
404        }
405
406        #endregion
407
408        #region ICollection<ILogTarget> Members
409
410        public void Add(ILogTarget item)
411        {
412            lock (List)
413                List.Add(item);
414        }
415
416        public void Clear()
417        {
418            lock (List)
419                List.Clear();
420        }
421
422        public bool Contains(ILogTarget item)
423        {
424            lock (List)
425                return List.Contains(item);
426        }
427
428        public void CopyTo(ILogTarget[] array, int arrayIndex)
429        {
430            lock (List)
431                List.CopyTo(array, arrayIndex);
432        }
433
434        public int Count
435        {
436            get
437            {
438                lock (List)
439                    return List.Count;
440            }
441        }
442
443        public bool IsReadOnly
444        {
445            get { return false; }
446        }
447
448        public bool Remove(ILogTarget item)
449        {
450            lock (List)
451                return List.Remove(item);
452        }
453
454        #endregion
455
456        #region IEnumerable<ILogTarget> Members
457
458        public IEnumerator<ILogTarget> GetEnumerator()
459        {
460            return List.GetEnumerator();
461        }
462
463        #endregion
464
465        #region IEnumerable Members
466
467        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
468        {
469            return List.GetEnumerator();
470        }
471
472        #endregion
473
474        /// <summary>
475        /// The backing store for this list.
476        /// </summary>
477        private List<ILogTarget> List = new List<ILogTarget>();
478    }
479
480    /// <summary>
481    /// The logger target interface which all interested listeners of log events must
482    /// implement.
483    /// </summary>
484    public interface ILogTarget
485    {
486        /// <summary>
487        /// The handler for events.
488        /// </summary>
489        /// <param name="sender">The sender of the event.</param>
490        /// <param name="e">The event data associated with the event.</param>
491        void OnEventLogged(object sender, LogEventArgs e);
492
493        /// <summary>
494        /// Chains the provided target to the current target, so that when this
495        /// target receives an event, the provided target is also executed.
496        /// </summary>
497        /// <param name="target">The target to chain with the current one.</param>
498        /// <remarks>Chaining a target multiple times will cause the target to
499        /// be invoked multiple times for every event.</remarks>
500        void Chain(ILogTarget target);
501
502        /// <summary>
503        /// Unchains the provided target from the current target, so that the
504        /// provided target is no longer invoked when this target receives an event.
505        /// </summary>
506        /// <param name="target">The target to unchain</param>
507        /// <remarks>Multiply-chained targets need to be unchained the same amount
508        /// of time to be completely removed.</remarks>
509        void Unchain(ILogTarget target);
510    }
511
512    /// <summary>
513    /// Registers a provided log target to receive log messages for the lifespan
514    /// of this object.
515    /// </summary>
516    public sealed class LogSession : IDisposable
517    {
518        /// <summary>
519        /// Constructor. Registers the given log target with the provided threads
520        /// for listening for log messages.
521        /// </summary>
522        /// <param name="target">The target that should receive events.</param>
523        /// <param name="threads">The threads which the target will be registered
524        /// with for event notifications.</param>
525        public LogSession(ILogTarget target, params Thread[] threads)
526        {
527            Target = target;
528            Threads = threads.Distinct().ToArray();
529
530            foreach (Thread thread in Threads)
531            {
532                if (!Logger.Listeners.ContainsKey(thread))
533                    Logger.Listeners.Add(thread, new LogThreadTargets());
534                Logger.Listeners[thread].Add(target);
535            }
536
537            Target.OnEventLogged(this, new LogEventArgs(
538                new LogEntry(S._("Session started"), LogLevel.Information)));
539        }
540
541        /// <summary>
542        /// Constructor. Registered the given log target with the current thread
543        /// for listening for log messages.
544        /// </summary>
545        /// <param name="target">The target which should receive events</param>
546        public LogSession(ILogTarget target)
547            : this(target, Thread.CurrentThread)
548        {
549        }
550
551        #region IDisposable Members
552
553        ~LogSession()
554        {
555            Dispose(false);
556        }
557
558        private void Dispose(bool disposing)
559        {
560            if (Threads == null || Target == null)
561                return;
562
563            if (disposing)
564            {
565                //Disconnect the event handler from the threads.
566                foreach (Thread thread in Threads)
567                    Logger.Listeners[thread].Remove(Target);
568            }
569
570            Target.OnEventLogged(this, new LogEventArgs(
571                new LogEntry(S._("Session ended"), LogLevel.Information)));
572            Threads = null;
573            Target = null;
574        }
575
576        public void Dispose()
577        {
578            Dispose(true);
579            GC.SuppressFinalize(this);
580        }
581
582        #endregion
583
584        /// <summary>
585        /// The target that should receive events. If this is null, the object
586        /// has been disposed.
587        /// </summary>
588        private ILogTarget Target;
589
590        /// <summary>
591        /// The list of threads which the target will be registered with for event
592        /// notifications. If this is null, the object is disposd.
593        /// </summary>
594        private Thread[] Threads;
595    }
596
597    /// <summary>
598    /// Collects a list of log entries into one session.
599    /// </summary>
600    /// <remarks>Instance functions of this class are thread-safe.</remarks>
601    [Serializable]
602    public class LogSink : ISerializable, ILogTarget, IList<LogEntry>
603    {
604        public LogSink()
605        {
606        }
607
608        #region ISerializable Members
609
610        public LogSink(SerializationInfo info, StreamingContext context)
611        {
612            List = (List<LogEntry>)info.GetValue("List", typeof(List<LogEntry>));
613        }
614
615        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
616        public void GetObjectData(SerializationInfo info, StreamingContext context)
617        {
618            info.AddValue("List", List);
619        }
620
621        #endregion
622
623        #region ILoggerTarget Members
624
625        public void OnEventLogged(object sender, LogEventArgs e)
626        {
627            lock (List)
628                List.Add(e.LogEntry);
629
630            lock (ChainedTargets)
631                ChainedTargets.ForEach(target => target.OnEventLogged(sender, e));
632        }
633
634        public void Chain(ILogTarget target)
635        {
636            lock (ChainedTargets)
637                ChainedTargets.Add(target);
638        }
639
640        public void Unchain(ILogTarget target)
641        {
642            lock (ChainedTargets)
643                ChainedTargets.Remove(target);
644        }
645
646        /// <summary>
647        /// The list of targets which are chained to this one.
648        /// </summary>
649        private List<ILogTarget> ChainedTargets = new List<ILogTarget>();
650
651        #endregion
652
653        #region IList<LogEntry> Members
654
655        public int IndexOf(LogEntry item)
656        {
657            lock (List)
658                return IndexOf(item);
659        }
660
661        public void Insert(int index, LogEntry item)
662        {
663            lock (List)
664                List.Insert(index, item);
665        }
666
667        public void RemoveAt(int index)
668        {
669            lock (List)
670                List.RemoveAt(index);
671        }
672
673        public LogEntry this[int index]
674        {
675            get
676            {
677                lock (List)
678                    return List[index];
679            }
680            set
681            {
682                lock (List)
683                    List[index] = value;
684            }
685        }
686
687        #endregion
688
689        #region ICollection<LogEntry> Members
690
691        public void Add(LogEntry item)
692        {
693            lock (List)
694                List.Add(item);
695        }
696
697        public void Clear()
698        {
699            lock (List)
700                List.Clear();
701        }
702
703        public bool Contains(LogEntry item)
704        {
705            lock (List)
706                return List.Contains(item);
707        }
708
709        public void CopyTo(LogEntry[] array, int arrayIndex)
710        {
711            lock (List)
712                List.CopyTo(array, arrayIndex);
713        }
714
715        public int Count
716        {
717            get
718            {
719                lock(List)
720                    return Count;
721            }
722        }
723
724        public bool IsReadOnly
725        {
726            get { return true; }
727        }
728
729        public bool Remove(LogEntry item)
730        {
731            lock (List)
732                return List.Remove(item);
733        }
734
735        #endregion
736
737        #region IEnumerable<LogEntry> Members
738
739        public IEnumerator<LogEntry> GetEnumerator()
740        {
741            return List.GetEnumerator();
742        }
743
744        #endregion
745
746        #region IEnumerable Members
747
748        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
749        {
750            return List.GetEnumerator();
751        }
752
753        #endregion
754
755        /// <summary>
756        /// Gets the highest log level in the current log sink.
757        /// </summary>
758        public LogLevel Highest
759        {
760            get
761            {
762                lock (List)
763                    return List.Max(delegate(LogEntry e) { return e.Level; });
764            }
765        }
766
767        /// <summary>
768        /// Gets the time the first message was logged.
769        /// </summary>
770        public DateTime StartTime
771        {
772            get
773            {
774                lock (List)
775                    return List.First().Timestamp;
776            }
777        }
778
779        /// <summary>
780        /// Gets the time the last message was logged.
781        /// </summary>
782        public DateTime EndTime
783        {
784            get
785            {
786                lock (List)
787                    return List.Last().Timestamp;
788            }
789        }
790
791        /// <summary>
792        /// The backing store of this session.
793        /// </summary>
794        private List<LogEntry> List = new List<LogEntry>();
795    }
796}
Note: See TracBrowser for help on using the repository browser.