[.NET][Log4net]解決FileAppender+MinimalLock效能問題(二)(MSMQ)

為了避免多個Process對同一個log檔案寫入而引發檔案鎖定或者相互覆蓋而出現log內容遺失問題,上一篇使用Buffer來解決,這一篇用MSMQ

 

2年多前參加國內某航空公司系統轉換專案的教育訓練,當時架構師規劃的log就用上了MSMQ

每一台ap都將log寫到自己的MSMQ,然後獨立的Log Server 每隔一段時間來收,這樣就可以將log集中管理,又免去臨時通訊中斷的問題。

 

 

1.首先要在Windows 啟用MSMQ並建立MSMQ Private Queue,可以參考以下兩篇:

[Powershell]啟用Windows Server MSMQ功能(訊息佇列)

[Powershell]建立MSMQ(訊息佇列)設定

 

假設我們已經按照上面建立了名稱為logqueue的private queue

2.log4net有一個msmq的sample code可以使用

打開前一篇的Visual Studio專案,新增一支class:  MsmqAppender.cs

#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Messaging;

using log4net.Core;

namespace Appender
{
	/// <summary>
	/// Appender writes to a Microsoft Message Queue
	/// </summary>
	/// <remarks>
	/// This appender sends log events via a specified MSMQ queue.
	/// The queue specified in the QueueName (e.g. .\Private$\log-test) must already exist on
	/// the source machine.
	/// The message label and body are rendered using separate layouts.
	/// </remarks>
	public class MsmqAppender : log4net.Appender.AppenderSkeleton
	{
		private MessageQueue m_queue;
		private string m_queueName;
		private log4net.Layout.PatternLayout m_labelLayout;

		public MsmqAppender()
		{
		}

		public string QueueName
		{
			get { return m_queueName; }
			set { m_queueName = value; }
		}

		public log4net.Layout.PatternLayout LabelLayout
		{
			get { return m_labelLayout; }
			set { m_labelLayout = value; }
		}

		override protected void Append(LoggingEvent loggingEvent) 
		{
			if (m_queue == null)
			{
				if (MessageQueue.Exists(m_queueName))
				{
					m_queue = new MessageQueue(m_queueName);
				}
				else
				{
					ErrorHandler.Error("Queue ["+m_queueName+"] not found");
				}
			}

			if (m_queue != null)
			{
				Message message = new Message();

				message.Label = RenderLabel(loggingEvent);

				using(System.IO.MemoryStream stream = new System.IO.MemoryStream())
				{
					System.IO.StreamWriter writer = new System.IO.StreamWriter(stream, new System.Text.UTF8Encoding(false, true));
					base.RenderLoggingEvent(writer, loggingEvent);
					writer.Flush();
					stream.Position = 0;
					message.BodyStream = stream;

					m_queue.Send(message);
				}
			}
		}

		private string RenderLabel(LoggingEvent loggingEvent)
		{
			if (m_labelLayout == null)
			{
				return null;
			}

			System.IO.StringWriter writer = new System.IO.StringWriter();
			m_labelLayout.Format(writer, loggingEvent);

			return writer.ToString();
		}
	}
}

 

3.接著設定log4net.config

<?xml version="1.0" encoding="utf-8" ?>
<log4net>

  <root>
    <level value="All"/>
    <appender-ref ref="MsmqAppender" />
  </root>

  <appender name="MsmqAppender" type="Appender.MsmqAppender">
    <queueName value="stanley14\private$\logqueue"/>
    <labelLayout value="LOG [%level] %date"/>
    <layout type="log4net.Layout.XmlLayoutSchemaLog4j"/>
  </appender>


</log4net>

 

4.測試程式: 執行1,000次寫log

private static readonly ILog Logger = LogManager.GetLogger(typeof(testLog4net));
[TestMethod]
public void TestMethod1()
{
    XmlConfigurator.Configure(new FileInfo(string.Format(@"{0}\{1}",
        Directory.GetParent(Directory.GetParent(Environment.CurrentDirectory).FullName),
        "log4net.config")));
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 1000; i++)
    {
        Logger.Debug(string.Format("this is no{0} log", i + 1));
    }
    sw.Stop();
    Console.WriteLine("總耗時(ElapsedMilliseconds) = " + sw.ElapsedMilliseconds);
}

 

測試結果: 1,000筆log 1.721秒,速度可!

Private queues\logqueue果然也出現1,000筆log queues

 

小結:

  • 從MQViewer可以發現,log4net寫出的log是xml格式,再找時間再改寫成json格式的log entry。
  • 這一篇先筆記將log寫到MSMQ,下一篇再筆記從MSMQ讀出,然後寫到Log File。

 

參考:

sample appender 

log4net

msmq sample code