扩展log4net的PatternLayout


一、需求 

PatternLayout的配置格式化如下所示:
< layout  type ="log4net.Layout.PatternLayout" >
     < conversionPattern  value ="[%date{yyyy-MM-dd HH:mm:ss}] [%level] %message %exception %newline"   />
</ layout >

由PatternLayout的conversionPattern来设置一个“模板”信息。其变量都有“%”开头的单词标识。如%level、%message;变量也可以传入参数,如%date{ yyyy-MM-dd HH:mm:ss },其中yyyy-MM-dd HH:mm:ss就是%date的参数。

PatternLayout默认所可以支持的单词标识,请参考:
http://www.cnitblog.com/seeyeah/archive/2008/10/15/50291.html

现在我们需要扩展这些单词标记。
假设模板字符串定义如下所示:
 [%date{yyyy-MM-dd HH:mm:ss}] %o{Message},%o{User} %newline"
注意%o{Message},%o是我们要实现扩展的一个标记,标识传入的message的对象,后面的参数{Message}、{User}表示message的对象的2个属性名。

我们先设计一个传递日志消息的实体类,定义如下所示
class SampleMessage
{
     public  string Message {  getset; }

     public  string User {  getset; }
}


如下调用ILog的Info方法。

log.Info( new SampleMessage() { Message = "Test1", User = "User1" });
log.Info( new SampleMessage() { Message = "Test2", User = "User2" });


我们想要的结果如下所示:
[2009-09-19 14:27:29] Test1,User1
[2009-09-19 14:27:29] Test2,User2 


二、方案

Log4net用appender来记录日志的加载方式,内置就有很多种appender:RollingFileAppender、ConsoleAppender、AdoNetAppender等等。其中,日志信息的格式由appender中的layout掌控,log4net内置的layout是log4net.Layout.PatternLayout。

Appender(具体以RollingFileAppender为例,其他类型Appender类似)与layout的关系如下所示:
 


RollingFileAppender的基类FileAppender以及TextWriterAppender是文件日志类的公用基类。AppenderSkeleton是所有log4net的appender的基类,内部封装了常用方法,如线程锁定、日志等级过滤和支持一般的文件写入等。

另外一边的PatternLayout,结构跟Appender相似,IAppender包含一个ILayout负责格式化日志的格式。因此如果我们要扩展日志的格式化,就需要扩展PatternLayout。

三、实现

下面说明,扩展PatternLayout的实现过程。

Step1:实现一个Converter

Log4net内置提供的每个模板参数,都有对应的Converter做处理。如%message对应MessagePatternConverter;%date对应DatePatternConverter等。这个对应关系可以查看log4net的源代码PatternLayout.cs的静态构造函数,内部用一个静态的Hashtable管理关键字与Converter的关系:

         ///   <summary>
        
///  Initialize the global registry
        
///   </summary>
        
///   <remarks>
        
///   <para>
        
///  Defines the builtin global rules.
        
///   </para>
        
///   </remarks>
         static PatternLayout()
        {
            s_globalRulesRegistry =  new Hashtable(45);

            s_globalRulesRegistry.Add("literal",  typeof(log4net.Util.PatternStringConverters.LiteralPatternConverter));
            s_globalRulesRegistry.Add("newline",  typeof(log4net.Util.PatternStringConverters.NewLinePatternConverter));
            s_globalRulesRegistry.Add("n",  typeof(log4net.Util.PatternStringConverters.NewLinePatternConverter));

            s_globalRulesRegistry.Add("c",  typeof(LoggerPatternConverter));
            s_globalRulesRegistry.Add("logger",  typeof(LoggerPatternConverter));

            s_globalRulesRegistry.Add("C",  typeof(TypeNamePatternConverter));
            s_globalRulesRegistry.Add("class",  typeof(TypeNamePatternConverter));
            s_globalRulesRegistry.Add("type",  typeof(TypeNamePatternConverter));

再看看我们将要实现的模板字符串:
[%date{yyyy-MM-dd HH:mm:ss}] %o{Message},%o{User} %newline"
我们要实现的关键字是“o”,按照log4net的PatternLayout的设计,我们也要相应实现一个Converter去解析关键字是“o”的内容,我们定义这个类名为ObjectConverter。

下面是ObjectConverter的实现方式,实现较为简单,详细看注释:

     ///   <summary>
    
///  根据键值获取值的对象
    
///   </summary>
     public  interface IGetObjectValueByKey
    {
         string GetByKey( string name);
    }
    
     ///   <summary>
    
///  对应%o的对象转换器
    
///   </summary>
    
///   <remarks> 用于PatternLayout </remarks>
     public  class ObjectConverter : PatternLayoutConverter
    {
         static Func< objectstringobject> funcs;
         static ObjectConverter()
        {
             // ********根据键值获取值的顺序
            
// 从接口获取值
            funcs += GetValueByInterface;
             // 反射获取属性值
            funcs += GetValueByReflection;
             // 从索引值获取值
            funcs += GetValueByIndexer;
        }

         ///   <summary>
        
///  实现PatternLayoutConverter.Convert抽象方法
        
///   </summary>
        
///   <param name="writer"></param>
        
///   <param name="loggingEvent"></param>
         protected  override  void Convert(TextWriter writer, LoggingEvent loggingEvent)
        {
             // 获取传入的消息对象
             object objMsg = loggingEvent.MessageObject;

             if (objMsg ==  null)
            {
                 // 如果对象为空输出log4net默认的null字符串
                writer.Write(SystemInfo.NullText);
                 return;
            }
             if( string.IsNullOrEmpty( this.Option))
            {
                 // 如果属性为空,输出消息对象的ToString()
                writer.Write(objMsg.ToString());
                 return;
            }

             object val = GetValue(funcs, objMsg, Option);
            writer.Write(val ==  null ? "" : val.ToString());
        }

         #region 静态方法
         ///   <summary>
        
///  循环方法列表,根据键值获取值
        
///   </summary>
        
///   <param name="func"> 方法列表委托 </param>
        
///   <param name="obj"> 对象 </param>
        
///   <param name="name"> 键值 </param>
        
///   <returns></returns>
         private  static  object GetValue(Func< objectstringobject> func,  object obj,  string name)
        {
             object val =  null;
             if (func !=  null)
            {
                 foreach (Func< objectstringobject> del  in func.GetInvocationList())
                {
                    val = del(obj, name);
                     // 如果获取的值不为null,则跳出循环
                     if (val !=  null)
                    {
                         break;
                    }
                }
            }
             return val;
        }

         ///   <summary>
        
///  使用接口方式取值
        
///   </summary>
        
///   <param name="obj"></param>
        
///   <param name="name"></param>
        
///   <returns></returns>
        
///   <remarks> 效率最高,避免了反射带来的效能损耗 </remarks>
         private  static  object GetValueByInterface( object obj,  string name)
        {
             object val =  null;
            IGetObjectValueByKey objConverter = obj  as IGetObjectValueByKey;
             if (objConverter !=  null)
            {
                val = objConverter.GetByKey(name);
            }
             return val;
        }

         ///   <summary>
        
///  反射对象的获取属性,获取属性值
        
///   </summary>
        
///   <param name="obj"></param>
        
///   <param name="name"></param>
        
///   <returns></returns>
         private  static  object GetValueByReflection( object obj,  string name)
        {
             object val =  null;
            Type t = obj.GetType();
            var propertyInfo = t.GetProperty(name);
             if (propertyInfo !=  null)
            {
                val = propertyInfo.GetValue(obj,  null);
            }

             return val;
        }

         ///   <summary>
        
///  反射对象的索引器,获取值
        
///   </summary>
        
///   <param name="obj"></param>
        
///   <param name="name"></param>
        
///   <returns></returns>
         private  static  object GetValueByIndexer( object obj,  string name)
        {
             object val =  null;

            MethodInfo getValueMethod = obj.GetType().GetMethod("get_Item");
             if (getValueMethod !=  null)
            {
                val = getValueMethod.Invoke(obj,  new  object[] { name });
            }

             return val;
        }
         #endregion
     }



主要是ObjectConvertert按顺序用了3种从键值获取值的方式
1、    对象实现了我们所定义的接口IGetObjectValueByKey,直接调用方法获取。此方法效率最好,因为内部避免了反射所带来的损耗。
2、    用反射获取属性值
3、    用反射获取索引值


Step2:把Converter注册到PatternLayout


现在我们需要把已实现的ObjectConverter加入PatternLayout的解析逻辑中。
我们先从ILog中找到藏在里面的PatternLayout实例,实现如下代码所示:

var log = Log4NetCommon.GetLog("LogConfig1st");
var appender = log.Logger.Repository.GetAppenders()[0];
var layout = ((appender  as AppenderSkeleton).Layout  as PatternLayout);

Log4NetCommon是我们自己用于初始化Log4Net的工具类。log是我们一般主打使用日志的ILog实例,第二行我们找到对应的Appender,最后通过转换类型找到了PatternLayout。

PatternLayout提供了AddConverter方法,可以轻松加入我们刚实现Converter。
AddConverter有2个签名版本:
public  void AddConverter(ConverterInfo converterInfo)
public  void AddConverter( string name, Type type)

根据源代码对AddConverter的解析
Programmatic users should use the alternative <see cref="AddConverter(string,Type)"/> method.
我们还是按要求调用AddConverter(string name, Type type)的版本。
layout.AddConverter("o",  typeof(ObjectConverter));


但目前还是不能解析模板中关键字“o”,为什么呢?

首先我们先简单了解一下PatternLayout如何、在什么时候注册关键字与Converter。

1、    在PatternLayout中,定义了一个s_globalRulesRegistry的静态Hashtable,Key为关键字,Value为对应的Converter类型。在PatternLayout的静态构造函数中,先会注册log4net内置的45个关键字。
2、    初始化log4net配置的时候,会调用PatternLayout的ActivateOptions初始化以上的配置,以及在ActivateOptions中会调用CreatePatternParser解析配置中的模板字符串。

在初始化log4net配置的时候,都还没来得及AddConverter,log4net就已经解析完毕了。因此在调用了AddConverter,即修改了所有有关PatternLayout配置时,必须再手动调用一次ActivateOptions重新解析一次模板字符串。

全部代码实现如下所示:

var log = Log4NetCommon.GetLog("LogConfig1st");

var appender = log.Logger.Repository.GetAppenders()[0];
var layout = ((appender  as AppenderSkeleton).Layout  as PatternLayout);
layout.AddConverter("o",  typeof(ObjectConverter));
layout.ActivateOptions();

实现完毕!


三、全部代码下载


Log4NetPatternLayoutExtension.zip
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值