log4net的大名早有耳闻,一直没真正用过,这次开发APP项目准备在服务端使用log4net。 日志的数据量较大,频繁的写数据库容易影响系统整体性能,所以独立将日志写到mongodb数据库是不错的选择。---经过2天的摸索,总结出本文档。



 



github有个开源项目log4mongo-net,另一位斯克迪亚作者根据开源项目又做了修改http://skyd.sinaapp.com/archives/1282



所以直接拿斯克迪亚的代码来使用。



 



1、将log4net和mongodb驱动升级为最新版本。log2net: 1.2.15   mongodb: 2.2.3.3



2、新加了一个LogHelper类,所有的日志通过LogHelper的静态方法来写。




1 public class Helper
 2     {
 3         private static readonly Helper instance = new Helper();
 4         private static ILog apiLog = null;
 5         private static ILog dbLog = null;
 6 
 7         private Helper()
 8         {
 9             // XmlConfigurator.Configure();    
10             LogLog.InternalDebugging = true;
11 
12 
13         }
14         public   static ILog  ApiLog()
15         {
16             if (apiLog == null)
17             {
18                 apiLog = LogManager.GetLogger("ApiLog");
19             }
20             return apiLog;
21 
22         }
23         public static ILog DbLog()
24         {
25             if (dbLog == null)
26             {
27                 dbLog = LogManager.GetLogger("ApiLog");
28             }
29             return dbLog;
30 
31         }
32     }



调用



Log4Mongo.Helper.DbLog().Info("abc");



 




 



3、自定义日志内容字段。网上能找到很多Log4net添加字段的方法,但因为我用的是修改过的log4mongo。而且简化了配置,默认就是显示所有字段。所以基本上通过改配置的都不适用。



开始准备使用 LogicalThreadContext.Properties["CustomColumn"] = "Custom value" ,可以将更多自定义的内容写到日志。



 执行写日志log.Debug(object)之前,先把各种自定义内容通过LogicalThreadContext设置好。然后再执行log.Debug(object)。



这种方式虽然可以达到目的,但感觉不是很方便。另一个问题是设置LogicalThreadContext后,这些值会一直存在,在同一个线程里再次执行 log.Debug(object)时,任然会记录这些自定义的属性值,不太好控制,容易写错内容。



 



另一种办法是通过 log.Debug(object)里的object来实现,一般情况下都是直接 log.Debug("this is message")这种方式来写日志,也可以将一个对象传递给 log.Debug。在往数据库写数据前,可以通过loggingEvent.RenderedMessage来读取传递过来的对象,


public  class LogInfo
 
  
    {
 
  
        public string AppKey { set; get; }
 
  
 
 
  
        public string UserID { set; get; }
 
  
        public string HostName { set; get; }
 
  
        public string IPAddress { set; get; }
 
  
        public string Message { set; get; }
 
  
 
 
  
        public override string ToString()
 
  
        {
 
  
            var bsonDoc = this.ToBsonDocument();
 
  
            return bsonDoc.ToString();
 
  
        }
 
  
    }


默认loggingEvent.RenderedMessage是获取object 的ToString()的值,这里需要重写一下ToString(),将类转化为BsonDocument。



loggingEvent.RenderedMessage获取BsonDocument后再拆分为具体的属性。
 
 
 
 
 
        private static void BuildCustomMessage(string message,ref Log log)
 
  
        {
 
  
            try
 
  
            {
 
  
                var bson = BsonDocument.Parse(message);
 
  
                foreach (var item in bson.Elements)
 
  
                {
 
  
                    string value = item.Value.ToString();
 
  
                    if (item.Value.IsBsonNull)
 
  
                    {
 
  
                        value = string.Empty;
 
  
                    }
 
  
                    log.Properties.Add(item.Name, value);
 
  
                }
 
  
            }
 
  
            catch (Exception)
 
  
            {
 
  
                log.RenderedMessage = message;
 
  
            }
 
  
 
 
  
        }



为了方便,直接用 BsonDocument.Parse(message)来判断是复杂对象还是String。直接用try catch来处理,对性能会有一点影响。做了测试相比直接传递String不需要BuildCustomMessage ,传递复杂对象时性能相差6%左右。这6%主要是对象ToString()转化为BsonDocument,和BuildCustomMessage两部份。



 



 



4、在测试时发生一个奇怪的问题,单独一个语句log.Debug(string)可以输出到控制台,但死活写不到数据库。而用一个循环来执行log.Debug(string),却能正常写到数据库。  log4net有输出Shutdown called on Hierarchy的提示,但不明白是什么意思,也不知道是否有相关。



解决办法是将写数据库的操作由异步改为同步。具体原因没找到。log4net自身问题、  mongodb驱动 、 log4net缓存等等都没法排除。


//collection.InsertOneAsync(BuildBsonDocument(loggingEvent));
 
 
 collection.InsertOne(BuildBsonDocument(loggingEvent));
 
 
 
 
 
 
 
 
5、为了方便日后的维护,将数据库名和Collection(表名)设置到配置文件。
 
 
<appender name="MongoDBAppender" type="Log4Mongo.MongoDBAppender, Log4Mongo">
 
  
  <connectionString value="mongodb://root:123456@localhost:27017"/>
 
  
  <DatabaseName value="log4mongo"/>
 
  
  <CollectionName value="yyyyMM"/>
 
  
</appender>



配置的值在public class MongoDBAppender : AppenderSkeleton这个类里会直接获取,不需要做额外处理



public string ConnectionStringName { get; set; }
 
  
    public string DatabaseName { get; set; }
 
  
     public string CollectionName { get; set; }



  <CollectionName value="yyyyMM"/>这么写的目的是让日志根据日期来自动分表。通过修改配置就能按日、按月、按年来分表。



 



private IMongoCollection<Log> GetCollection()
		{
            string tableName;
            tableName = CollectionName ?? "yyyyMM";
            tableName = "log"+ string.Format("{0:" + tableName + "}", DateTime.Now);
            return GetDatabase().GetCollection<Log>(tableName); 
		}