65.9K
CodeProject 正在变化。 阅读更多。
Home

EasyLogger:使日志记录变得更容易

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (8投票s)

2015 年 12 月 14 日

CPOL

8分钟阅读

viewsIcon

15139

downloadIcon

244

这是一个轻量级工具,可在预定义或定制格式以及便捷的时间戳记功能之外,实现类似JAVA的便捷日志记录。

引言

日志进度/问题/状态是软件开发中用于评估、测试和故障排除的重要组成部分,这个简单的实用工具可以通过以下方式为您的编码带来极大的便利:

  • 记录不同级别的不同消息,而无需输入过多内容,就像JAVA开发人员熟悉的Log.e、Log.w、Log.i、Log.d和Log.v分别代表ERROR、WARN、INFO、DEBUG和VERBOSE一样。
  • 在开发过程中,只需在源代码中一次性编写这些Log语句,然后通过简单调整日志记录器的设置,即可提取任何级别的不同日志。
  • 自动生成样式化的消息,以区分不同级别的消息。
  • 使多个日志记录器同时工作。
  • 保留时间戳以评估执行时长,并使用多个队列来计算任何操作的间隔。

背景

此工具最初是为了通过使用一个秒表为不同字符串标识的任何操作标记时间戳来衡量某些密集计算的性能而开发的。

无需键入Debug.WriteLine()或CW(Console.WriteLine)之类的样板代码,然后对其进行注释/取消注释,而是使用Log.x(e/w/i/d/v,类似于Android开发者)的形式,通过不同的日志级别标记消息,并提供完全可定制的行为,这个小工具为我节省了大量的打字时间。为了使这个工具更方便,同一行日志可以触发多个日志记录器独立工作,并且由于事件背后的机制,这个目标并不难实现。最后,正如最初设想的,使用一个StopWatch,一些由字符串标识的列表可以用来记录时间/间隔信息,否则这些信息将需要大量冗余的代码。

本文旨在解释如何使用此工具,并介绍实现这些理想功能所用的一些技术。

一般信息

工具包库中仅包含3个文件:LogLevel.cs、ILog.cs和Log.cs。

为了遵循JAVA世界的约定,ENUM LogLevel 定义如下

    [Flags]
    public enum LogLevel
    {
        NONE = 0,

        // Priority constant for the println method; use Log.v.
        VERBOSE = 1,

        // Priority constant for the println method; use Log.d.
        DEBUG = 2,

        // Priority constant for the println method; use Log.i.
        INFO = 4,

        // Priority constant for the println method; use Log.w.
        WARN = 8,

        // Priority constant for the println method; use Log.e.
        ERROR = 16, 

        WARN_AND_ABOVE = WARN | ERROR,
        INFO_AND_ABOVE = INFO | WARN_AND_ABOVE,
        DEBUG_AND_ABOVE = DEBUG | INFO_AND_ABOVE,
        ALL = VERBOSE | DEBUG_AND_ABOVE
    } 

每个日志记录器都有一个名为 ConcernedLogs 的属性,用于决定应记录哪些级别的消息:选择预定义的ALL/XXX_AND_ABOVE组合可以达到仅保留特定级别以上消息的效果。然而,也可以仅记录特定级别的消息。例如,通过将 ConcernedLogs 指定为 VERBOSE|ERROR,DEBUG/INFO/WARN 级别的消息将被筛选掉。

ILog接口定义了实现应具有的行为如下

    public interface ILog
    {
        //Hook method to define the means to record the message
        PerformLogging DoLogging { get; }

        //Min LogLevel to be recorded by calling DoLogging
        LogLevel ConcernedLogs { get; set; }

        //Hook method to uniformly generate tags of the specific LogLevels.
        Func<LogLevel, string> BuildTag { get; }

        //Hook method to convert the stacktrace of an Exception 
        Func<Exception, StackTrace, string> BuildStacktrace { get; }

        //Hook method to combine TAG and DETAILS together as messages to be recorded
        Func<string, string, string> BuildFinalMessage { get; }

        //Method to log VERBOSE level details with default TAG
        void v(string details);
        //Method to log DEBUG level details with default TAG
        void d(string details);
        //Method to log INFO level details with default TAG
        void i(string details);
        //Method to log WARN level details with default TAG
        void w(string details);
        //Method to log ERROR level details with default TAG
        void e(string details);

        //Method to log VERBOSE level details with customer specific TAG
        void v(string tag, string details);
        //Method to log DEBUG level details with customer specific TAG
        void d(string tag, string details);
        //Method to log INFO level details with customer specific TAG
        void i(string tag, string details);
        //Method to log WARN level details with customer specific TAG
        void w(string tag, string details);
        //Method to log ERROR level details with customer specific TAG
        void e(string tag, string details);

        //Method to log exception
        void e(Exception exception, LogLevel? stacktraceLevel = null);
    }

PerformLogging 实际上是一个委托,定义如下,它是一个函数委托,也是一个事件处理器

    //Delegate of general message logging handler
    public delegate void PerformLogging(LogLevel logLevel, string message);

 

与依赖变量的传统方法不同,函数委托用于定义日志记录器的行为。这始终是我最喜欢的方式,可以获得最大的灵活性,详细的实现将在后面介绍。

每个日志记录器定义了3组方法

  1. 具有单个参数的日志记录函数:详细信息将被归类为某个LogLevel(v(something)将被标记为VERBOSE,i(...)将被标记为INFO,d()/w()/e()分别代表DEBUG/WARN/ERROR),然后根据ConcernedLogs,委托DoLogging、BuildTag和BuildFinalMessage会创建对应LogLevel的标签和最终输出消息,并带有默认模板。因此,v("some message")的显示效果将不同于d("some message")。
  2. 对于那些喜欢自己定义有意义标签的人,可以选择第二组方法(例如void d(string tag, string details)),它接受两个字符串参数。
  3. 异常可能包含一些对开发人员最有用的信息,值得特别处理。使用 e(Exception exception, LogLevel? stacktraceLevel=null),堆栈跟踪将被转换为字符串,由 Func<Exception, StackTrace, string> BuildStacktrace 生成,并在不为null时作为stacktraceLevel保存。

这些函数只能与实例关联,因为接口的限制。实际上,带有大写名称的静态方法将释放此工具的强大功能。

  1. V(string details)、D()、I()、W()和E()将分别触发所有日志记录器的v()/d()/i()/w()/e()。
  2. V(string format, params object[] args)、D(...)、I(...)、W(...)和E(...)将没有实例对应项,它们等同于V(string.Format(string format, params object[] args))……如果您期望一个静态版本的void d(string tag, string details),这可能会令人困惑,因为这会引入一些冲突,而且我个人认为集成string.Format()会更有帮助。
  3. 静态void E(Exception ex, LogLevel? stackTraceLevel = LogLevel.INFO) 将触发所有日志记录器以堆栈跟踪方式记录异常,并在stackTraceLevel不为null时输出源代码。

 

实现

使用委托定义一个Log实例

为了效率考虑,有时我会直接将行为保留为属性,而不是保留许多描述性信息。在Log.cs中,实例字段/属性定义如下

        #region Properties and Hooks
        public LogLevel ConcernedLogs { get; set; }
        public PerformLogging DoLogging { get; protected set; }
        public Func<LogLevel, string> BuildTag { get; protected set; }
        public Func<string, string, string> BuildFinalMessage { get; protected set; }
        public Func<Exception, StackTrace, string> BuildStacktrace { get; protected set; }
        public string Descripiton { get; set; }
       #endregion

ConcernedLogs,如前所述,用于通过唯一的执行方法过滤消息级别。

        /// <summary>
        /// Centralized mechanism to create tag based on the LogLevel and compose
        /// and log the final message.
        /// </summary>
        /// <param name="logLevel">Level of the logging request.</param>
        /// <param name="details">Logging details to be reserved.</param>
        protected virtual void log(LogLevel logLevel, string details)
        {
            if (DoLogging != null && (logLevel & ConcernedLogs) != 0)
            {
                string tag = BuildTag(logLevel);
                switch(logLevel)
                {
                    case LogLevel.VERBOSE:
                        v(tag, details);
                        break;
                    case LogLevel.DEBUG:
                        d(tag, details);
                        break;
                    case LogLevel.INFO:
                        i(tag, details);
                        break;
                    case LogLevel.WARN:
                        w(tag, details);
                        break;
                    case LogLevel.ERROR:
                        e(tag, details);
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }
        }

 

v(tag, details) 将简单地将 VERBOSE 标记为消息的 LogLevel,消息由 tag 和 details 组成,如下所示,d()...e() 也是如此。

        public virtual void v(string tag, string details)
        {
            DoLogging(LogLevel.VERBOSE, BuildFinalMessage(tag, details));
        }

 

因此,三个函数委托 BuildTag/BuildFinalMessage/DoLogging 定义了如何创建标签,然后将标签与详细信息一起附加为最终消息,并分别保存/输出。

对于 Func<LogLevel, string> BuildTag,给定一个特定的 LogLevel,它将组合一个字符串作为 Tag,以某种特殊格式标记详细信息。可以参考一些预定义的静态函数(FormattedTag、NoTag、ShortTag & FullTag)直接使用,ShortTag(LogLevel logLevel) 将分别为不同的 LogLevel 返回 "[V]"/"[D]"..."[E]",而 FormattedTag(LogLevel logLevel)

        public static string FormattedTag(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.VERBOSE:
                case LogLevel.DEBUG:
                    return string.Format("{0}[{1}]", SpaceBeforeTag, logLevel.ToString().First());
                case LogLevel.INFO:
                    return string.Format("[I:{0}]", ElapsedTimeString());
                case LogLevel.WARN:
                    return string.Format("*[W:{0}]", ElapsedTimeString());
                case LogLevel.ERROR:
                    return string.Format("***[ERROR:{0}@{1}]", ElapsedTimeString(), DateTime.Now.ToString("HH:mm:ss"));
                default:
                    throw new NotImplementedException();
            }
        }

将显示不同的消息,带有不同的前缀,以便于快速定位消息,例如 ERRORs:您只需查看以"***[ERROR:..."开头的日志。

 

对于 Func<string, string, string> BuildFinalMessage { get; protected set; },它提供了一种灵活的方式将 tag 和 details 组合成最终消息。默认实现如下所示。

        public static Func<string, string, string> DefaultFinalMessage =
            (tag, details) => string.Format("{0}{1}{2}", tag, TagMessageConnector, details);

您可以提供更丰富的功能,根据需要包含时间/源/项#等额外信息。

 

对于 PerformLogging DoLogging { get; protected set; },其签名来自下方,并暗示它是一个事件处理器。

    //Delegate of general message logging handler
    public delegate void PerformLogging(LogLevel logLevel, string message);

 

注意:消息可以被视为 Tag + Details。

它可以像默认实现一样简单

        public static PerformLogging DefaultDoLogging = (level, msg) => Console.WriteLine(msg);

 

或者可以与StringBuffer关联,如测试样本所示,或者以反向顺序输出到具有完全访问控制和异常处理的文件流中,这些无法在此描述。

将它们放在一起,仅以 LogLevel 和 DoLogging 作为必需参数,就可以使用此构造函数定义一个 Log 实例。

        public Log(LogLevel concernedLogs, PerformLogging doLogging, 
            Func<LogLevel, string> buildTag=null
            , Func<Exception, StackTrace, string> buildStacktrace = null
            , Func<string, string, string> buildMessage = null)
        {
            if (doLogging == null)
                throw new ArgumentNullException("Logging means must be specified!");
            this.ConcernedLogs = concernedLogs;
            this.DoLogging = doLogging;
            this.BuildTag = buildTag ?? DefaultTag;
            this.BuildFinalMessage = buildMessage??DefaultFinalMessage;
            this.BuildStacktrace = buildStacktrace??DefaultStacktrace;
            this.Descripiton = description ?? string.Format("Log{0}", this.GetHashCode());

            OnLoggingEvent += this.log;
            OnExceptionEvent += this.logException;
        }

然后,Log 实例将拥有一套相当完善的行为来处理不同的消息。然而,创建和引用这些实例几乎无法为日志记录带来明显的好处,调用静态方法来触发所有已创建 Log 实例的日志记录是我期望的方法,其实现基于事件机制。

基于事件的日志记录

根据 Microsoft Events Tutorial事件为对象提供了一种非常实用的方式来信号化状态变化,这可能对该对象的使用者有用。事件是创建可在大量不同程序中重用的类的关键构建块。

用于触发消息日志记录的事件定义为静态事件,如下所示。

        private static event PerformLogging onLoggingEvent;
        public static event PerformLogging OnLoggingEvent
        {
            add
            {
                lock (DefaultDoLogging)
                {
                    if (onLoggingEvent == null || !onLoggingEvent.GetInvocationList().Contains(value))
                    {
                        onLoggingEvent += value;
                    }
                }
            }
            remove
            {
                lock (DefaultDoLogging)
                {
                    if (value != null && onLoggingEvent != null && onLoggingEvent.GetInvocationList().Contains(value))
                    {
                        onLoggingEvent -= value;
                    }
                }
            }
        }

 

事件只能通过调用两个 private raiseEvent() 方法之一来引发。

        private static void raiseEvent(LogLevel level, string details)
        {
            try
            {
                if (onLoggingEvent != null)
                    onLoggingEvent(level, details);
            }
            catch (Exception ex)
            {
                //Assume the default log is still working
                try
                {
                    Instance.e(ex, LogLevel.INFO);
                }
                catch { }
            }
        }

        private static void raiseEvent(LogLevel level, string format, params object[] args)
        {
            try
            {
                if (format == null)
                    throw new ArgumentNullException("format cannot be null!");
                if (onLoggingEvent != null)
                {
                    string details = string.Format(format, args);
                    onLoggingEvent(level, details);
                }
            }
            catch (Exception ex)
            {
                //Assume the default log is still working
                try
                {
                    Instance.e(ex, LogLevel.INFO);
                }
                catch { }
            }
        }

它们仅由大写字母开头的静态方法调用,例如记录 VERBOSE 消息。

        public static void V(string details)
        {
            raiseEvent(LogLevel.VERBOSE, details);
        }

        public static void V(string format, params object[] args)
        {
            raiseEvent(LogLevel.VERBOSE, format, args);
        }

因为 Log 实例的构造函数会将 this.log 钩接到静态事件。

            OnLoggingEvent += this.log;
            OnExceptionEvent += this.logException;

 

使用 "Log.V(...)" 或 "Log.D(...)" 会触发 OnLoggingEvent,所有已注册的日志会分别调用相关的 "this.v()" 和 "this.d()"。因此,完全可以定义多个 Log 实例,调用这些静态方法会使它们像递归调用其实例方法一样工作。当不再需要日志记录时,只需取消注册事件处理程序即可,而无需修改源代码。(虽然我包含了在 Log 实例被释放时取消挂钩这些事件的代码,但没有测试来验证这一点)。

例如,像这样定义一个扩展的 Log 类。

    public class MemLog : Log
    {
        StringBuilder sb = new StringBuilder();

        public MemLog(LogLevel minLogLevel,
            Func<LogLevel, string> buildTag = null
            , Func<Exception, StackTrace, string> buildStacktrace = null
            , Func<string, string, string> buildMessage = null)
        {
            this.ConcernedLogs = minLogLevel;
            this.DoLogging = (level, msg) => sb.Insert(0, msg + "\r\n");
            this.BuildTag = buildTag ?? DefaultTag;
            this.BuildFinalMessage = buildMessage ?? DefaultFinalMessage;
            this.BuildStacktrace = buildStacktrace;

            OnLoggingEvent += this.log;
            OnExceptionEvent += this.logException;
        }

        public string GetLog()
        {
            return sb.ToString();
        }
    }

一个简单的测试方法

        [TestMethod]
        public void TestMethod3()
        {
            Log.I("Change the DefaultLogLevel to " + Log.ChangeDefaultLogLevel(LogLevel.ALL));
            var memLog = new MemLog(LogLevel.VERBOSE | LogLevel.WARN,
                Log.FullTag,
                Log.DebuggableStacktrace
                );
            t1(3);

            Console.WriteLine("\r\n------------------------------------------");
            Console.WriteLine("MemLog shows:");
            Console.WriteLine(memLog.GetLog());
        }

将产生如下输出:

Result StandardOutput:    
[I:038ms]: Change the DefaultLogLevel to DEBUG_AND_ABOVE
  [D]: t1
[I:039ms]: v=3
  [V]: verbose from t3
***[ERROR:039ms@23:07:10]: To throw Exception in t3
***[ERROR:040ms@23:07:10]: Exception of type 'System.Exception' was thrown.
*[W:040ms]: StackTrace
  t3: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 55
    t2: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 45
      t1: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 34
        TestMethod3: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 110

  [V]: t1 done!

------------------------------------------
MemLog shows:
[VERBOSE]: t1 done!
[WARN]: StackTrace
  t3: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 55
    t2: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 45
      t1: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 34
        TestMethod3: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 110

[VERBOSE]: verbose from t3

Memlog 将仅以与默认 Log 相反的顺序保留 VERBOSE 和 WARN 消息。

异常的日志记录

异常可能是解决问题的最有价值的来源,因此除了使用 Log.E() 或 somelog.e() 将消息记录为 ERROR 级别外,ILog 接口还声明了一个处理 Exception 的方法。

        public virtual void e(Exception exception, LogLevel? stacktraceLevel = null)
        {
            logException(exception, new StackTrace(true), stacktraceLevel);
        }

与 this.log() 类似,logException() 方法将在调用 log() 之前执行一些预处理。

        protected virtual void logException(Exception exception, StackTrace stacktrace, LogLevel? stacktraceLevel = null)
        {
            if(exception == null || DoLogging == null)
                return;

            log(LogLevel.ERROR, exception.Message);

            if (!stacktraceLevel.HasValue || BuildStacktrace == null)
                return;
            LogLevel level = stacktraceLevel.Value;
            if ((level & ConcernedLogs) == 0)
                return;
            string stacktraceString = BuildStacktrace(exception, stacktrace);
            log(level, "StackTrace\r\n" + stacktraceString);
        }

Func<Exception, StackTrace, string> BuildStacktrace { get; protected set; } 用于从抛出的异常中提取堆栈跟踪信息,将给定的堆栈跟踪字符串作为 LogLevel 输出,由 stacktraceLevel 指定。对于可能涉及调用底层库的数十次调用的 Microsoft 默认堆栈跟踪信息感到不适,下面的方法将用作默认值,仅记录可调试的代码,如前一个示例的输出所示。

        public static string DebuggableStacktrace(Exception exception, StackTrace stacktrace)
        {
            if (stacktrace == null)
                throw new ArgumentNullException();
            StringBuilder sb = new StringBuilder();
            StackFrame[] frames = stacktrace.GetFrames();
            int i = 0;
            foreach(StackFrame frame in frames)
            {
                string fileName = frame.GetFileName();
                if (fileName == logClassFilename)
                    continue;

                int lineNumber = frame.GetFileLineNumber();
                if(lineNumber > 0)
                {
                    sb.AppendFormat("{0}{1}: {2}, line {3}\r\n", new string(' ', (++i)*2), frame.GetMethod().Name, fileName, lineNumber);
                }
            }
            return sb.ToString();
        }

在静态构造函数中使用 logClassFilename 来保留 Log 类的文件名。

        protected static string logClassFilename = null;
        static Log()
        {
            RestartStopwatch();

            StackTrace stackTrace = new StackTrace(true);
            StackFrame thisFrame = stackTrace.GetFrame(0);
            logClassFilename = thisFrame.GetFileName();
        }

为了支持基于事件的异常日志记录,定义了一组类似的委托、事件和静态方法,如下所示。

    //Delegate of Exception logging handler
    public delegate void PeformExceptionLogging(Exception exception, StackTrace stacktrace, LogLevel? stacktraceLevel = null);

        private static event PeformExceptionLogging onExceptionEvent;
        public static event PeformExceptionLogging OnExceptionEvent {add{...} remove{...}}

        private static void raiseEvent(Exception exception, StackTrace stacktrace, LogLevel? stacktraceLevel = null)
        {
            try
            {
                if (onExceptionEvent != null)
                {
                    onExceptionEvent(exception, stacktrace, stacktraceLevel);
                }
            }
            catch (Exception ex)
            {
                //Assume the default log is still working
                try
                {
                    Instance.e(ex, LogLevel.INFO);
                }
                catch { }
            }
        }

        public static void E(Exception ex, LogLevel? stackTraceLevel = LogLevel.INFO)
        {
            raiseEvent(ex, new StackTrace(true), stackTraceLevel);
        }


万能秒表

如前所述,这个工具是在我尝试使用一个 Stopwatch 实例通过将滴答声保存在字符串键引用的长整型列表中来测量多个操作时开发的,用于保留和测量经过的时间。

        private static Stopwatch stopwatch = new Stopwatch();

        protected static long createMoment = DateTime.Now.Ticks;
        protected static Dictionary<string, List<long>> MomentsRepository = new Dictionary<string, List<long>>();

        public static ICollection<string> GetMomentIds()
        {
            return MomentsRepository.Keys;
        }

        public static int MarkMoment(string momentId = null)
        {
            if (momentId == null)
            {
                StackTrace stacktrace = new StackTrace(true);
                StackFrame callerFrame = stacktrace.GetFrames().FirstOrDefault(frame =>
                    frame.GetFileName() != logClassFilename && frame.GetFileLineNumber() != 0);
                momentId = callerFrame == null ? "Unknown"
                    : string.Format("{0}: {1}_L{2}", callerFrame.GetFileName(), callerFrame.GetMethod().Name, callerFrame.GetFileLineNumber());
            }
            if (!MomentsRepository.ContainsKey(momentId))
            {
                MomentsRepository.Add(momentId, new List<long>());
            }
            long ticks = DateTime.Now.Ticks;
            List<long> moments = MomentsRepository[momentId];
            moments.Add(ticks);
            return moments.Count;
        }

然后,可以方便地获取记录的时刻以及这些时刻之间的经过时间,如下所示。

        public static long[] GetMoments(string momentId, IEnumerable<int> indexes=null)
        {
            if (momentId == null)
                throw new ArgumentNullException("momentId cannot be null to retrieve the concerned moments.");
            //Returns empty array if no such moments recorded
            if (!MomentsRepository.ContainsKey(momentId))
                return new long[0];

            if (indexes == null)
                return MomentsRepository[momentId].ToArray();

            var moments = MomentsRepository[momentId];
            List<int> qualifiedIndexes = indexes.Where(i => i >= 0 && i < moments.Count).ToList();
            var result = qualifiedIndexes.Select(i => moments[i]).ToArray();
            return result;
        }
        
        public static long[] GetIntervals(string momentId, IEnumerable<int> indexes=null)
        {
            if (momentId == null)
                throw new ArgumentNullException("momentId cannot be null to retrieve the concerned moments.");
            //Returns empty array if no such moments recorded
            if (!MomentsRepository.ContainsKey(momentId))
                return new long[0];

            var moments = MomentsRepository[momentId];
            if (indexes == null)
            {
                var intervals = moments.Skip(1).Select((v, index) => v - moments[index-1]).ToArray();
                return intervals;
            }
            List<int> qualifiedIndexes = indexes.Where(i => i > 0 && i < moments.Count).ToList();

            long[] selectedIntervals = qualifiedIndexes.Select(i => moments[i] - moments[i-1]).ToArray();
            return selectedIntervals;
        }

或者,将入口和出口时刻记录到两个字符串键的两个列表中,可能更容易进行后处理。

使用代码

源代码附带了一个测试项目,展示了如何使用此工具。

例如,MemLog.cs 显示了一个 Log 类的扩展,如下所示。

    public class MemLog : Log
    {
        StringBuilder sb = new StringBuilder();

        public MemLog(LogLevel minLogLevel,
            Func<LogLevel, string> buildTag = null
            , Func<Exception, StackTrace, string> buildStacktrace = null
            , Func<string, string, string> buildMessage = null)
        {
            this.ConcernedLogs = minLogLevel;
            this.DoLogging = (level, msg) => sb.Insert(0, msg + "\r\n");
            this.BuildTag = buildTag ?? DefaultTag;
            this.BuildFinalMessage = buildMessage ?? DefaultFinalMessage;
            this.BuildStacktrace = buildStacktrace;

            OnLoggingEvent += this.log;
            OnExceptionEvent += this.logException;
        }

        public string GetLog()
        {
            return sb.ToString();
        }
    }

然后,测试方法声明一个实例,所有对 Log.cs 静态方法的调用都会以反向顺序将 VERBOSE 和 WARN 消息保留在 StringBuilder 中。

        [TestMethod]
        public void TestMethod3()
        {
            Log.I("Change the DefaultLogLevel to " + Log.ChangeDefaultLogLevel(LogLevel.ALL));
            var memLog = new MemLog(LogLevel.VERBOSE | LogLevel.WARN,
                Log.FullTag,
                Log.DebuggableStacktrace
                );
            t1(3);

            Console.WriteLine("\r\n------------------------------------------");
            Console.WriteLine("MemLog shows:");
            Console.WriteLine(memLog.GetLog());
        }

测试的输出将是:

Test Name:    TestMethod3
Test Outcome:    Passed
Result StandardOutput:    
[I:000ms]: Restart stopwatch at 12/14/2015 11:59:08 PM, with time elapsed of 000ms
[I:006ms]: Change the DefaultLogLevel to DEBUG_AND_ABOVE
  [D]: t1
[I:007ms]: v=3
  [V]: verbose from t3
***[ERROR:008ms@23:59:08]: To throw Exception in t3
***[ERROR:009ms@23:59:08]: Exception of type 'System.Exception' was thrown.
*[W:009ms]: StackTrace
  t3: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 55
    t2: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 45
      t1: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 34
        TestMethod3: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 110

  [V]: t1 done!

------------------------------------------
MemLog shows:
[VERBOSE]: t1 done!
[WARN]: StackTrace
  t3: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 55
    t2: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 45
      t1: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 34
        TestMethod3: d:\Trial\EasyLogger\EasyLoggerTest\UnitTest1.cs, line 110

[VERBOSE]: verbose from t3

很容易看出,在不大量修改源代码的情况下,以另一种形式记录消息可以多么方便。

最新源代码(如有更新)也可以在 https://github.com/Cruisoring/EasyLog/tree/master/EasyLogger 找到。

 

历史

2015/12/15:首次发布。

© . All rights reserved.