NetDh框架適用於C/S、B/S的服務端框架,可用於項目開發和學習。目前包含如下四個模塊html
1.數據庫操做層封裝Dapper,支持多種數據庫類型、多庫實例,簡單強大;git
此部分具體說明可參考博客: http://www.javashuo.com/article/p-yiwcfheq-k.htmlgithub
2.提供簡單高效的日誌操做類使用,支持日誌寫入Db和txt、支持任何數據庫類型寫入(包括傳統sql數據庫和nosql數據庫等)、支持同步寫入日誌和後臺獨立線程異步處理日誌隊列;web
此部分具體說明可參考博客: 本文如下章節內容。sql
3.提供簡單緩存設計和使用;數據庫
此部分具體說明可參考博客: http://www.javashuo.com/article/p-nleneqwb-dz.html緩存
4.業務邏輯層服務簡單設計,可方便支持二次開發模式。安全
此部分具體說明可參考博客: http://www.javashuo.com/article/p-nleneqwb-dz.html多線程
NetDh.EasyLogger.LogHandle是一個輕便快捷的日誌操做類。併發
1.支持日誌寫入數據庫和txt文件;
2.支持全部數據庫類型的寫入日誌,包括傳統sql數據庫和nosql數據庫等(開放委託給調用方) ;
3.支持同步寫入日誌,也支持後臺獨立線程異步處理日誌任務,後臺線程數可經過構造函數配置。在構造函數中的asynThreadCount參數指定,asynThreadCount是異步隊列處理日誌的線程數,0表示同步處理;大於0表示後臺開asynThreadCount個線程異步處理日誌任務隊列。普通日誌量推薦默認的1,這樣系統可異步處理日誌,若是日誌出錯也是會記錄到本地txt;若是日誌量較多,再酌情設置大一些。
4.支持多個日誌操做對象,好比想把用戶操做日誌和系統日誌分開在不一樣表裏記錄,則能夠再聲明一個日誌操做對象。
直接上源碼(以源碼中的註釋做爲說明):
1 using System; 2 using System.Collections.Concurrent; 3 using System.IO; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace NetDh.EasyLogger 9 { 10 /* 11 * 此LogHandle是一個輕便快捷的日誌操做類。 12 * 1.支持日誌寫入數據庫和txt文件; 13 * 2.支持全部數據庫類型的寫入日誌,包括傳統sql數據庫和nosql數據庫等,由於是開放"Db寫入的委託"給調用方:) ; 14 * 3.支持同步寫入日誌,也支持後臺獨立線程異步處理日誌任務。 15 * 說明: 16 * 此日誌操做類可支持95%以上的場景。但不適用的場景是大併發超大量日誌寫入,這種狀況須要考慮緩存隊列、批次寫入、故障處理等。 17 * 通常的,超大量的日誌,有點失去了「日誌」的意義,由於很難分析。 18 * 總之,不要用此類來作大併發超大量數據寫入。 19 */ 20 21 /// <summary> 22 /// 輕便快捷的日誌操做類 23 /// </summary> 24 public class LogHandle 25 { 26 #region 屬性 27 /// <summary> 28 /// 日誌記錄者 29 /// </summary> 30 public string Recorder { get; set; } 31 /// <summary> 32 /// txt日誌的目錄;若是不須要記錄到txt則爲null 33 /// </summary> 34 public string DirectoryForTxt { get; set; } 35 /// <summary> 36 /// 定義寫入日誌到數據庫的委託;若是不須要記錄到數據庫則爲null 37 /// </summary> 38 public Action<string, TbLog> DoInsertLogToDb { get; set; } 39 /// <summary> 40 /// 異步隊列處理日誌的線程數。0表示同步處理;1表示後臺開一個線程異步處理日誌任務隊列.. 41 /// (建議異步處理的線程不須要太多,按日誌量:1到2個線程就好。) 42 /// </summary> 43 protected int AsynThreadCount { get; set; } 44 /// <summary> 45 /// 須要寫入日誌的隊列。 46 /// (BlockingCollection多線程安全隊列,可自動阻塞線程,默認是Queue結構) 47 /// </summary> 48 protected BlockingCollection<object> LogQueue = new BlockingCollection<object>(); 49 /// <summary> 50 /// 默認insert Sql語句。調用方可修改InsertLogSql,好比若是是oracle數據庫,則要把InsertLogSql語句中的@改成: 51 /// (表名稱可自定義。1 支持不一樣的表命名規則;2 支持實例化不一樣的表名稱對象用於多表日誌記錄(好比分操做日誌和系統後臺日誌等)) 52 /// </summary> 53 public string InsertLogSql = @" insert into {0}(Message,Recorder,LogLevel,LogCategory,CreateTime,Thread,LogUser,Ip) values (@Message,@Recorder,@LogLevel,@LogCategory,@CreateTime,@Thread,@LogUser,@Ip) "; 54 #endregion 55 56 #region 構造函數,配置日誌 57 /// <summary> 58 /// 日誌操做類,支持保存在數據庫和本地txt 59 /// </summary> 60 /// <param name="recorder">日誌記錄者</param> 61 /// <param name="directoryForTxt">winform程式參考:Path.Combine(Environment.CurrentDirectory, "Logs"); 62 /// web程式參考:System.Web.Hosting.HostingEnvironment.MapPath("~/Logs")</param> 63 /// <param name="logToDbAction">日誌寫入數據庫的委託。由調用方自動選擇db日誌寫入方式,這樣就可支持任何數據庫類型寫入日誌</param> 64 /// <param name="asynThreadCount">異步隊列處理日誌的線程數。0表示同步處理;大於0表示後臺開asynThreadCount個線程異步處理日誌任務隊列。普通日誌量推薦默認的1,這樣系統可異步處理日誌,若是日誌出錯也是會記錄到本地tx;若是日誌量較多,可設置大一些。</param> 65 /// <param name="logTableName">日誌表名,表名稱默認是TbLog,能夠自定義,好比TbLog等。1. 爲了避免同的表命名規則;2. 爲了支持多表日誌記錄(好比分操做日誌和系統後臺日誌等)。</param> 66 /// <param name="needStartLog">實例化日誌對象時,是否記錄一條start日誌</param> 67 public LogHandle(string recorder, string directoryForTxt = "", Action<string, TbLog> logToDbAction = null, 68 int asynThreadCount = 1, string logTableName = "TbLog", bool needStartLog = true) 69 { 70 if (string.IsNullOrWhiteSpace(directoryForTxt) && logToDbAction == null) 71 { 72 throw new Exception("沒有指定任何日誌記錄方式"); 73 } 74 Recorder = recorder; 75 DirectoryForTxt = directoryForTxt; 76 //初始化時確保日誌文件夾存在,以後寫入txt不用一直判斷 77 if (!string.IsNullOrWhiteSpace(DirectoryForTxt) && !Directory.Exists(DirectoryForTxt)) 78 { 79 Directory.CreateDirectory(DirectoryForTxt); 80 } 81 DoInsertLogToDb = logToDbAction; 82 //指定日誌表名 83 InsertLogSql = string.Format(InsertLogSql, logTableName); 84 AsynThreadCount = asynThreadCount; 85 //若是AsynThreadCount>=0,則異步處理日誌寫入;若是若是AsynThreadCount<=0,則是同步寫入日誌。 86 InitQueueConsume(); 87 if (needStartLog) 88 { 89 if (!string.IsNullOrWhiteSpace(DirectoryForTxt)) 90 { 91 LogToTxt(string.Format("init loghandle:{0}", Recorder), "start"); 92 } 93 if (DoInsertLogToDb != null) 94 { 95 LogToDb(string.Format("init loghandle:{0}", Recorder), "start"); 96 } 97 } 98 } 99 /// <summary> 100 /// 初始化異步處理隊列 101 /// </summary> 102 protected virtual void InitQueueConsume() 103 { 104 for (int i = 0; i < AsynThreadCount; i++)//AsynThreadCount<=0的話,不會進入循環 105 { 106 Task.Factory.StartNew(() => 107 { 108 //GetConsumingEnumerable 若是隊列中沒有項,會自動阻塞等待Add。這個線程會一直在後臺佔用。 109 foreach (var item in LogQueue.GetConsumingEnumerable()) 110 { 111 try 112 { 113 if (item is string) 114 { 115 DoInsertLogToTxt(item.ToString()); 116 } 117 else 118 { 119 DoInsertLogToDb(InsertLogSql, (TbLog)item); 120 } 121 } 122 catch (Exception e) 123 {//若是在處理任務過程失敗,須要捕獲以繼續處理下一個任務 124 } 125 } 126 }); 127 } 128 } 129 #endregion 130 131 #region Log、LogToDb、LogToTxt、LogToBoth 132 /// <summary> 133 /// 日誌優先寫入Db,當寫入Db失敗,纔會寫入txt。若是DoInsertLogToDb爲null,則會自動選擇寫入txt。 134 /// (這也是最經常使用的模式,太多日誌是不建議寫入txt) 135 /// </summary> 136 /// <param name="msg">日誌信息</param> 137 /// <param name="category">自定義類別</param> 138 /// <param name="level">日誌等級:Info,Warn,Error,Fatal,Debug</param> 139 /// <param name="user"></param> 140 /// <param name="ip"></param> 141 public virtual void Log(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "") 142 { 143 if (DoInsertLogToDb != null) 144 { 145 try 146 { 147 LogToDb(msg, category, level, user, ip); 148 } 149 catch (Exception e) 150 { 151 var exMsg = "-------------執行Log中的LogToDb時異常:" + LogHandle.GetExceptionDetailMsg(e); 152 if (!string.IsNullOrWhiteSpace(DirectoryForTxt))//若是寫入數據庫失敗,則寫入本地txt 153 { 154 LogToTxt(exMsg); 155 LogToTxt(msg, category, level, user, ip); 156 } 157 else 158 { 159 throw new Exception(exMsg); 160 } 161 } 162 } 163 else if (!string.IsNullOrWhiteSpace(DirectoryForTxt)) 164 { 165 LogToTxt(msg, category, level, user, ip); 166 } 167 } 168 /// <summary> 169 /// 日誌記錄到Db中。 170 /// </summary> 171 public virtual void LogToDb(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "") 172 { 173 var sqlParams = new TbLog 174 { 175 Message = msg, 176 Recorder = Recorder, 177 LogLevel = level.ToString(), 178 LogCategory = category, 179 CreateTime = DateTime.Now, 180 Thread = Thread.CurrentThread.ManagedThreadId, 181 LogUser = user, 182 Ip = ip 183 }; 184 if (AsynThreadCount <= 0) 185 {//同步處理 186 DoInsertLogToDb(InsertLogSql, sqlParams); 187 } 188 else 189 {//異步處理 190 LogQueue.Add(sqlParams); 191 } 192 } 193 194 /// <summary> 195 /// 日誌記錄到txt中。 196 /// </summary> 197 /// <param name="msg">日誌信息</param> 198 /// <param name="category">自定義類別</param> 199 /// <param name="level">日誌等級:Info,Warn,Error,Fatal,Debug</param> 200 /// <param name="user"></param> 201 /// <param name="ip"></param> 202 public virtual void LogToTxt(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "") 203 { 204 var threadId = Thread.CurrentThread.ManagedThreadId; 205 StringBuilder sb = new StringBuilder(); 206 sb.AppendFormat("[Thread]:{0} [Recorder]:{1} [Msg]:{2} ", threadId, Recorder, msg); 207 if (!string.IsNullOrWhiteSpace(category)) 208 { 209 sb.AppendFormat("[Category]:{0}", category); 210 } 211 if (level != EnLogLevel.Info) 212 { 213 sb.AppendFormat("[Level]:{0}", level.ToString()); 214 } 215 if (!string.IsNullOrWhiteSpace(user)) 216 { 217 sb.AppendFormat("[User]:{0}", user); 218 } 219 if (!string.IsNullOrWhiteSpace(ip)) 220 { 221 sb.AppendFormat("[Ip]:{0}", ip); 222 } 223 224 if (AsynThreadCount <= 0) 225 {//同步處理 226 DoInsertLogToTxt(sb.ToString()); 227 } 228 else 229 {//異步處理 230 LogQueue.Add(sb.ToString()); 231 } 232 } 233 private Object _lockWriteTxt = new object(); 234 /// <summary> 235 /// 日誌記錄到txt中。 236 /// (注意,此日誌處理類,是爲了支持普通量txt日誌寫入。若是是大併發寫入txt,則要另外設計此場景的txt寫入方式) 237 /// </summary> 238 /// <param name="strLog">須要記錄的信息</param> 239 public virtual void DoInsertLogToTxt(string strLog) 240 { 241 strLog = string.Format("{0} {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), strLog); 242 //天天一個txt文件,若是須要能夠改爲每小時一個文件 243 string logPath = Path.Combine(DirectoryForTxt, string.Format(@"Log{0}.txt", DateTime.Now.ToString("yyyyMMdd"))); 244 lock (_lockWriteTxt) 245 { 246 //這邊實現場景是一條一條日誌記錄。不適用大併發超大量txt寫入,這種狀況要另外設計此場景的txt寫入方式,好比要考慮緩存隊列、批次寫入、故障處理等。 247 using (FileStream fs = new FileStream(logPath, FileMode.OpenOrCreate, FileAccess.Write)) 248 { 249 using (StreamWriter sw = new StreamWriter(fs)) 250 { 251 sw.BaseStream.Seek(0, SeekOrigin.End); 252 sw.WriteLine(strLog); 253 sw.Flush(); 254 } 255 } 256 } 257 } 258 /// <summary> 259 /// 日誌寫入Db和txt。 260 /// </summary> 261 /// <param name="msg">日誌信息</param> 262 /// <param name="category">自定義類別</param> 263 /// <param name="level">日誌等級:Info,Warn,Error,Fatal,Debug</param> 264 /// <param name="user"></param> 265 /// <param name="ip"></param> 266 public virtual void LogToBoth(string msg, string category = "", EnLogLevel level = EnLogLevel.Info, string user = "", string ip = "") 267 { 268 try 269 { 270 LogToDb(msg, category, level, user, ip); 271 } 272 catch (Exception e) 273 { 274 LogToTxt("-------------執行LogToBoth中的LogToDb時異常:" + e.Message); 275 LogToTxt(msg, category, level, user, ip); 276 return; 277 } 278 LogToTxt(msg, category, level, user, ip); 279 } 280 #endregion 281 282 /// <summary> 283 /// 生成自定義異常消息,包含異常的堆棧 284 /// </summary> 285 /// <param name="ex">異常對象</param> 286 /// <returns>異常字符串文本</returns> 287 public static string GetExceptionDetailMsg(Exception ex) 288 { 289 StringBuilder sb = new StringBuilder(); 290 sb.AppendFormat("異常時間:{0}", DateTime.Now); 291 sb.AppendFormat("異常信息:{0}", ex.Message); 292 sb.AppendLine(string.Empty); 293 sb.AppendFormat("異常堆棧:{0}", ex.StackTrace); 294 sb.AppendLine(string.Empty); 295 return sb.ToString(); 296 } 297 } 298 }
直接看代碼和註釋:
1 /// <summary> 2 /// NetDh模塊使用示例代碼 3 /// </summary> 4 public class NetDhExample 5 { 6 #region 用全局靜態變量實現單例。 7 /// <summary> 8 /// 服務端使用數據庫操做對象 9 /// </summary> 10 public static DbHandleBase DbHandle { get; set; } 11 /// <summary> 12 /// 日誌操做對象 13 /// </summary> 14 public static LogHandle LogHandle { get; set; } 15 16 //說明:好比若是你想把用戶操做日誌和系統日誌分開在不一樣表裏記錄,則能夠再聲明一個日誌操做對象 17 public static LogHandle SysLogHandle { get; set; } 18 #endregion 19 /// <summary> 20 /// 靜態構造函數,只會初始化一次 21 /// </summary> 22 static NetDhExample() 23 { 24 25 //初始化數據庫操做對象 26 var connStr = "Data Source=.;Initial Catalog=Test;User Id=sa;Password=***;"; 27 DbHandle = new SqlServerHandle(connStr); 28 //若是有多庫,可再new個對象 29 //ReadDbHandle = new SqlServerHandle(connStrForRead); 30 31 //初始化日誌操做對象 32 //先定義日誌寫入數據庫的委託 33 Action<string, TbLog> doInsert = (sql, model) => 34 { 35 DbHandle.ExecuteNonQuery(sql, model);//你想要用什麼方式把日誌寫入Db,是能夠本身指定。 36 //DbHandle.Insert(model); 37 //若是你的表結構和TbLog類同樣,則可直接用:DbHandle.Insert(model);這樣就不會用到InsertLogSql,也就不用管InsertLogSql的語法是否支持全部數據庫. 38 }; 39 //其中的asynThreadCount參數默認是1,表明後臺獨立線程獨立處理日誌;我這邊設置爲0,表明同步處理日誌。 40 LogHandle = new LogHandle("MyLocalTest.exe", Path.Combine(Environment.CurrentDirectory, "Logs"), doInsert, 0, "TbLog"); 41 //若是你想要有多個日誌操做對象,則再new一個,把日誌放不一樣目錄不一樣數據表中 42 SysLogHandle = new LogHandle("MyLocalTest.exe", Path.Combine(Environment.CurrentDirectory, "SysLogs"), doInsert, 0, "TbSysLog"); 43 } 44 /// <summary> 45 /// 各模塊使用的示例代碼 46 /// </summary> 47 public static void TestMain() 48 { 49 #region 日誌處理類 50 LogHandle.LogToTxt("日誌寫入txt"); 51 LogHandle.LogToTxt("日誌寫入txt", "logcategory1");//可用第二個參數來自定義分類日誌 52 LogHandle.LogToDb("日誌寫入db", "logcategory2");//可用第二個參數來自定義分類日誌 53 LogHandle.LogToBoth("日誌同時寫入txt和Db"); 54 //LogHandle.Log是最經常使用的函數,太多日誌是不建議寫入txt。 55 LogHandle.Log("日誌優先寫入Db,當寫入Db失敗,纔會寫入txt。若是LogHandle對象DoInsertLogToDb屬性爲null,則會自動選擇寫入txt。"); 56 #endregion 57 } 58 }
國外有github,國內有碼雲,在國內使用碼雲速度很是快。NetDh框架源碼放在碼雲上:
https://gitee.com/donghan/NetDh-Framework
異步隊列處理日誌的線程數。0表示同步處理;大於0表示後臺開asynThreadCount個線程異步處理日誌任務隊列.普通日誌量推薦默認的1,這樣系統可異步處理日誌,若是日誌出錯也是會記錄到本地tx;,若是日誌量較多,可設置大一些。