source: branches/eraser6/CodeReview/Eraser.Util/Logger.cs @ 1777

Revision 1777, 15.1 KB checked in by lowjoel, 5 years ago (diff)

Redesigned the Logger paradigm -- instead of a dealing with a logger object to do any form of logging, we have a static Logger class which code can log to, and define log targets to receive those logs. Also included is a LogSession? class which sets the log target for the current thread or for a few threads. Addresses #320: Logging improvements

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