回到目錄
之前的講過兩篇關于日志元件的文章,分别是《第一回 日志記錄元件之自主的Vlog》和《第三回 日志記錄元件之log4net》,而今天主要說一下我自己開發的另一種日志元件Logger.Core,它也屬于面試AOP(橫切關注點)的一部分,這個元件對于一些想學習設計模式的同學來說,無疑是一個大餐!Logger.Core項目裡内含了政策模式,模版方法模式,工廠模式和單例模式,可以說,最常用的模式都用到了,而它們在這個項目裡都起到了什麼作用,什麼時候用到它們呢,這些答案相信在看完我的文章之後,您會有一個明确的答案的。
一 面向接口程式設計與多态
面向接口程式設計,是實作軟體解耦的靈魂,也是實作多态的方法之一,日志項目有統一的接口規範

/// <summary>
/// 日志功能接口規範
/// </summary>
public interface ILogger
{
/// <summary>
/// 記錄代碼運作時間
/// </summary>
/// <param name="message">消息</param>
/// <param name="action">所測試的代碼塊</param>
/// <param name="fileName">日志檔案名</param>
void Logger_Timer(string message, Action action, string fileName);
/// <summary>
/// 記錄代碼運作時間,日志檔案名以codeTime開頭的時間戳
/// </summary>
/// <param name="message">消息</param>
/// <param name="action">所測試的代碼塊</param>
void Logger_Timer(string message, Action action);
/// <summary>
/// 記錄代碼運作異常
/// </summary>
/// <param name="message">消息</param>
/// <param name="action">要添加try...catch的代碼塊</param>
/// <param name="fileName">日志檔案名</param>
void Logger_Exception(string message, Action action, string fileName);
/// <summary>
/// 記錄代碼運作異常,日志檔案名以Exception開頭的時間戳
/// </summary>
/// <param name="message">消息</param>
/// <param name="action">要添加try...catch的代碼塊</param>
void Logger_Exception(string message, Action action);
/// <summary>
/// 将message記錄到日志檔案
/// </summary>
/// <param name="message"></param>
void Logger_Info(string message);
/// <summary>
/// 将message記錄到名為fileName的日志檔案
/// </summary>
/// <param name="message"></param>
/// <param name="fileName"></param>
void Logger_Info(string message, string fileName);
}
View Code
二 繼承與面向對象
繼承是面向對象的三大特性之一,有了它,面向對象才有了層次感,将公共的功能點從各個派生類抽出,提取到基類中

/// <summary>
/// 日志核心基類
/// 模版方法模式,對InputLogger開放,對其它日志邏輯隐藏,InputLogger可以有多種實作
/// </summary>
internal abstract class LoggerBase : ILogger
{
private string _defaultLoggerName = DateTime.Now.ToString("yyyyMMddhh") + ".log";
/// <summary>
/// 日志檔案位址
/// 優化級為mvc方案位址,網站方案位址,console程式位址
/// </summary>
protected string FileUrl
{
get
{
try
{
return System.Web.HttpContext.Current.Server.MapPath("/Logger/"
+ System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString()
+ "/"
+ System.Web.HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString()
+ "/"); //例如:c:\\project\\Logger\\Home\\Index\\
}
catch (NullReferenceException)
{
try
{
return System.Web.HttpRuntime.AppDomainAppPath + "LoggerDir\\"; //例如:c:\\project\\
}
catch (ArgumentNullException)
{
return Environment.CurrentDirectory + "\\LoggerDir\\"; //例如:c:\\project\\bin\\debug
}
}
}
}
protected abstract void InputLogger(string message, string fileName);
#region ILogger 成員
public void Logger_Timer(string message, Action action, string fileName)
{
StringBuilder str = new StringBuilder();
Stopwatch sw = new Stopwatch();
sw.Restart();
str.Append(message);
action();
str.Append("代碼段運作時間:" + sw.ElapsedMilliseconds + "毫秒");
InputLogger(str.ToString(), string.IsNullOrWhiteSpace(fileName)
? "CodeTime" + _defaultLoggerName
: fileName);
sw.Stop();
}
public void Logger_Timer(string message, Action action)
{
Logger_Timer(message, action, null);
}
public void Logger_Exception(string message, Action action, string fileName)
{
try
{
action();
}
catch (Exception ex)
{
InputLogger("代碼段出現異常,資訊為" + ex.Message, string.IsNullOrWhiteSpace(fileName)
? "Exception" + _defaultLoggerName
: fileName);
}
}
public void Logger_Exception(string message, Action action)
{
Logger_Exception(message, action, null);
}
public void Logger_Info(string message)
{
InputLogger(message, null);
}
public void Logger_Info(string message, string fileName)
{
InputLogger(message, string.IsNullOrWhiteSpace(fileName)
? "Logger" + _defaultLoggerName
: fileName);
}
#endregion
}
三 模版方式模式規定具體流程,抽象個性化方法
對于個性化的操作聲明為抽象方法,在基類中實作統一的操作流程,在各個派生類中去實作個性化的子產品,這正是模版方式模式的展現
四 政策模式以多種方式實作某個功能
對于檔案持久化的方式有很多,而你可以分别去實作它,不知不覺中,我們正在使用政策模式來開發應用程式
普通持久化

/// <summary>
/// 以普通的文字流的方式寫日志
/// </summary>
internal class NormalLogger : LoggerBase
{
protected override void InputLogger(string message, string fileName)
{
string filePath = FileUrl + (fileName ?? "logger.log");
string dir = filePath.Substring(0, filePath.LastIndexOf("\\"));
if (!System.IO.Directory.Exists(dir))
{
System.IO.Directory.CreateDirectory(dir);
}
using (System.IO.StreamWriter srFile = new System.IO.StreamWriter(filePath, true))
{
srFile.WriteLine(message);
srFile.Close();
srFile.Dispose();
}
}
}
log4net實作日志分級的持久化

/// <summary>
/// Function:以log4net元件的方式寫日志
/// Remark:日志記錄方法可以使用第三方元件,如log4net
/// Author:zhangzhanling
/// Blogs:www.cnblogs.com/lori
/// </summary>
internal class Log4Logger : LoggerBase
{
/// <summary>
/// log4net配置檔案路徑
/// </summary>
static string _logConfig = System.Web.HttpContext.Current.Server.MapPath("/log4net.config");
static Log4Logger()
{
log4net.Config.XmlConfigurator.ConfigureAndWatch(new System.IO.FileInfo(_logConfig));
}
#region Prviate Methods
/// <summary>
/// 寫日志方法
/// </summary>
/// <param name="message"></param>
/// <param name="fileName"></param>
protected override void InputLogger(string message, string fileName)
{
string filePath = FileUrl + fileName;
var iLog = log4net.LogManager.GetLogger("Core.Logger");
ChangeLog4netLogFileName(iLog, filePath, message);
}
private void ChangeLog4netLogFileName(log4net.ILog iLog, string filePath, string message)
{
log4net.Core.LogImpl logImpl = iLog as log4net.Core.LogImpl;
if (logImpl != null)
{
var ac = ((log4net.Repository.Hierarchy.Logger)logImpl.Logger).Appenders;
var rfa = ac[0] as log4net.Appender.RollingFileAppender;
if (rfa != null)
{
string dir = filePath.Substring(0, filePath.LastIndexOf("\\"));
if (!System.IO.Directory.Exists(dir))
{
System.IO.Directory.CreateDirectory(dir);
}
rfa.File = filePath;
// 更新Writer屬性
rfa.Writer = new System.IO.StreamWriter(rfa.File, rfa.AppendToFile, rfa.Encoding);
rfa.Writer.WriteLine(message);
rfa.Writer.Close();
rfa.Writer.Dispose();
rfa.Close();
}
}
}
#endregion
}
五 工廠模式動态生産對象,單例模式保持對象執行個體的唯一性
當我們以多種方式實作了對日志的持久化後,我們可以通過工廠模式動态的在這些持久化方式之間實作切換,對象實作單例之後,減少了記憶體開銷,使對象的屬性成為了全局性屬性!

/// <summary>
/// 日志生産類
/// Singleton模式和政策模式和工廠模式
/// </summary>
public class LoggerFactory : ILogger
{
/// <summary>
/// 對外不能建立類的執行個體
/// </summary>
private LoggerFactory()
{
string loggerType = System.Configuration.ConfigurationManager.AppSettings["LoggerType"] ?? "NormalLogger";
switch (loggerType)
{
case "NormalLogger":
iLogger = new NormalLogger();
break;
case "Log4Logger":
iLogger = new Log4Logger();
break;
default:
throw new ArgumentException("日志方法不正确,目前隻支援NormalLogger和Log4Logger");
}
//(ILogger)Assembly.Load("Logger.Core").CreateInstance("Logger.Core." + className.Trim());
}
#region Logger有多種實作時,需要使用Singleton模式
private static object lockObj = new object();
private static LoggerFactory instance = null;
private ILogger iLogger = null;
/// <summary>
/// Get singleton instance of IoCFactory
/// </summary>
public static LoggerFactory Instance
{
get
{
if (instance == null)
{
lock (lockObj)
{
if (instance == null)
{
instance = new LoggerFactory();
}
}
}
return instance;
}
}
#endregion
#region ILogger 成員
public void Logger_Timer(string message, Action action, string fileName)
{
iLogger.Logger_Timer(message, action, fileName);
}
public void Logger_Timer(string message, Action action)
{
iLogger.Logger_Timer(message, action);
}
public void Logger_Exception(string message, Action action, string fileName)
{
iLogger.Logger_Exception(message, action, fileName);
}
public void Logger_Exception(string message, Action action)
{
iLogger.Logger_Exception(message, action);
}
public void Logger_Info(string message)
{
iLogger.Logger_Info(message);
}
public void Logger_Info(string message, string fileName)
{
iLogger.Logger_Info(message, fileName);
}
#endregion
}
最後有一句話送給大家:堅持,其實就是一種勝利!
作者:倉儲大叔,張占嶺,
榮譽:微軟MVP
QQ:853066980
支付寶掃一掃,為大叔打賞!