├── App.config ├── LogService.csproj ├── LogServiceHelper.cs ├── Program.cs └── README.md /App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LogService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F5682E77-B356-4721-A298-6BCBC121CDD0} 8 | Exe 9 | Properties 10 | LogService 11 | LogService 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /LogServiceHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace LogService 11 | { 12 | /** 13 | * 日志写入组件(**多线程写日志队列,单线程写日志文件**) 14 | * created by 王军华 20170207 599871338 15 | * 使用方法: 16 | * 在Global.asax的Application_Start方法中调用: Utility.LogServiceHelper.Intance.Start(); 17 | * 在使用的地方调用 LogServiceHelper.Intance.PutLog(new LogModel() { CreatedTime=DateTime.Now, Dir="operator", Msg=log.URL, Operator=log.LoginName }); 18 | * 在关闭的地方调用LogServiceHelper.Intance.Stop(); 19 | * 功能描述:1.多类型日志自动归类,将不同类型的日志写入不同的文件中。例如:登录日志,操作日志,错误日志等 20 | * 2.批量写入日志,减少IO操作 21 | * 3.延时写入功能,当日志大小小于某个阈值时不写入 22 | * 4.强制写入功能,当日志在批处理列表中保留时间超过某个阈值时强制写入 23 | * 5.日志分割(按天,按体积分割) 24 | * */ 25 | public class LogServiceHelper 26 | { 27 | private LogServiceHelper() { } 28 | public static readonly LogServiceHelper Intance = new LogServiceHelper(); 29 | private bool _isStart = false; 30 | private Thread _thread = null; 31 | private int _maxByteSize = 1024 * 64;//64KB,当单个缓存日志达到多少byte时写入文件 32 | private int _maxFileSize = 1024 * 1024 * 1;//1MB,单个日志文件的大小 33 | private int _maxSecond = 30;//最大相隔时间,不管缓存区中的日志大小到不到64K都写入硬盘 34 | private int _maxWriteErrorTime = 10;//最大连续写入错误次数 35 | private int _currentWriteErrorTime = 0;//当前连续写入错误次数 36 | private string _baseDirectory = string.Empty; 37 | private LogModel _tmpLog = null; 38 | private ConcurrentQueue queue = new ConcurrentQueue();//日志队列 39 | //对不同类型的日志进行区分合并 40 | private List _logBatch = new List();//批处理列表 41 | private LogBatch _tmpLogBatch = null; 42 | /// 43 | /// 开始写入日志,系统初始化时调用:向磁盘写入日志需要满足同一类型日志条数>=batchCount条或者同一类型日志存在超过maxSecond秒 44 | /// 45 | /// 日志保存的基目录(全路径),例如:System.Web.HttpContext.Current.Server.MapPath("~/logs/") 46 | /// 当日志缓存队列日志达到多少byte时写入文件,默认64KB 47 | /// 单个日志文件大小,默认1M 48 | /// 当同一类型日志存在且超过maxSecond秒时向磁盘写入日志 49 | public void Start(string baseDirectory, int maxByteSize = 65536, int maxFileSize = 1048576, int maxSecond = 30) 50 | { 51 | this._baseDirectory = baseDirectory; 52 | this._maxByteSize = maxByteSize; 53 | this._maxFileSize = maxFileSize; 54 | this._maxSecond = maxSecond; 55 | _thread = new Thread(TastExecuting); 56 | _thread.IsBackground = true; 57 | _thread.Start(); 58 | } 59 | public void AgainStart() 60 | { 61 | _isStart = true; 62 | } 63 | public void Stop() 64 | { 65 | _isStart = false; 66 | } 67 | private void TastExecuting() 68 | { 69 | _isStart = true; 70 | while (_isStart) 71 | { 72 | 73 | if (queue.Count > 0) 74 | { 75 | QueueOut(); 76 | } 77 | LogBatchWrite(); 78 | 79 | } 80 | } 81 | public long GetQueueCount() 82 | { 83 | return queue.Count; 84 | } 85 | private void QueueOut() 86 | { 87 | queue.TryDequeue(out _tmpLog); 88 | if (_tmpLog != null) 89 | { 90 | //不同类型的日志放在不同的对象中 91 | _tmpLogBatch = _logBatch.FirstOrDefault(c => c.Dir == _tmpLog.Dir); 92 | if (_tmpLogBatch == null) 93 | { 94 | _tmpLogBatch = new LogBatch(); 95 | _tmpLogBatch.StartTime = DateTime.Now; 96 | _tmpLogBatch.Dir = _tmpLog.Dir; 97 | _logBatch.Add(_tmpLogBatch); 98 | } 99 | _tmpLogBatch.Count += 1; 100 | _tmpLogBatch.LogStr = _tmpLogBatch.LogStr.AppendFormat("时间【{0}】,操作人【{1}】,信息【{2}】" + Environment.NewLine, _tmpLog.CreatedTime.ToString("yyyy-MM-dd HH:mm ss"), _tmpLog.Operator, _tmpLog.Msg); 101 | _tmpLog = null; 102 | } 103 | } 104 | private void LogBatchWrite() 105 | { 106 | //缓存日志,定期写入文件,空间换时间 避免频繁操作硬盘 造成IO开销过大 107 | foreach (var item in _logBatch) 108 | { 109 | int second = (DateTime.Now - item.StartTime).Seconds; 110 | if (item.LogStr.Length > _maxByteSize || second >= _maxSecond) 111 | { 112 | //写入日志 113 | if (WriteFile(item.LogStr.ToString(), item.Dir)) 114 | { 115 | item.StartTime = DateTime.Now; 116 | item.Count = 0; 117 | item.LogStr.Clear(); 118 | } 119 | } 120 | } 121 | } 122 | 123 | /// 124 | /// 关闭线程 125 | /// 126 | public void Abort() 127 | { 128 | _isStart = false; 129 | _thread.Abort(); 130 | } 131 | /// 132 | /// 日志写入 133 | /// 134 | /// 日志类型,如登录日志(log),错误日志(error),操作日志(operator) 135 | /// 日志内容 136 | /// 操作人 137 | public void Write(string logType, string content, string userName = "") 138 | { 139 | queue.Enqueue(new LogModel() { Dir = logType, Msg = content, Operator = userName }); 140 | } 141 | public void Write(LogModel log) 142 | { 143 | queue.Enqueue(log); 144 | } 145 | private bool WriteFile(string log, string sDirectory) 146 | { 147 | if (string.IsNullOrEmpty(log)) 148 | { 149 | return true; 150 | } 151 | //c:\log\login\2016\1\20160111.log 152 | DateTime now = DateTime.Now; 153 | string year = now.Year.ToString(); 154 | string month = now.Month.ToString(); 155 | string dir = Path.Combine(_baseDirectory, year, month); 156 | try 157 | { 158 | if (!Directory.Exists(dir)) 159 | { 160 | Directory.CreateDirectory(dir); //如果文件夹不存在,则创建一个新的 161 | } 162 | else 163 | { 164 | 165 | //request_2017年02月13日(0).log 166 | //获取文件夹dir下的所有文件 167 | int index = 0; 168 | var fileList = Directory.GetFiles(dir).ToList(); 169 | if (fileList.Count > 0) 170 | { 171 | index = fileList.Count(c => c.Contains(sDirectory)); 172 | if (index > 0) 173 | { 174 | index--; 175 | } 176 | } 177 | string filename = sDirectory + "_" + now.ToString("yyyy年MM月dd日") + "(" + index + ").log"; 178 | string fileNameNew = Path.Combine(dir, filename); //文件不存在则创建 179 | if (!System.IO.File.Exists(fileNameNew)) 180 | { 181 | WriteFile(log, fileNameNew, true); 182 | } 183 | else 184 | { 185 | //获取文件大小 186 | FileInfo fileInfo = new FileInfo(fileNameNew); 187 | if (fileInfo.Length > _maxFileSize)//如果大于单个文件最大体积 188 | { 189 | fileNameNew = fileNameNew.Replace("(" + index + ").log", "(" + (index + 1) + ").log"); 190 | if (!System.IO.File.Exists(fileNameNew)) 191 | { 192 | WriteFile(log, fileNameNew, true); 193 | } 194 | else 195 | { 196 | WriteFile(log, fileNameNew, false); 197 | } 198 | } 199 | else 200 | { 201 | 202 | WriteFile(log, fileNameNew, false); 203 | } 204 | 205 | } 206 | 207 | } 208 | _currentWriteErrorTime = 0; 209 | return true; 210 | } 211 | catch (Exception ex) 212 | { 213 | _currentWriteErrorTime++; 214 | Thread.Sleep(10000);//暂停10秒 215 | if (_currentWriteErrorTime > _maxWriteErrorTime) 216 | { 217 | Stop();//停止写入日志,发送报警短信 218 | } 219 | return false; 220 | } 221 | finally 222 | { 223 | 224 | } 225 | 226 | } 227 | private void WriteFile(string log, string filePath, bool isCreated) 228 | { 229 | FileStream file = null; 230 | if (isCreated) 231 | { 232 | file = new FileStream(filePath, FileMode.CreateNew); 233 | } 234 | else 235 | { 236 | file = new FileStream(filePath, FileMode.Append); 237 | } 238 | StreamWriter sw = new StreamWriter(file, Encoding.Default); 239 | sw.Write(log); 240 | sw.Flush(); 241 | sw.Close(); 242 | file.Close(); 243 | } 244 | } 245 | [Serializable] 246 | public class LogModel 247 | { 248 | public LogModel() 249 | { 250 | CreatedTime = DateTime.Now; 251 | } 252 | public DateTime CreatedTime { get; set; } 253 | public string Msg { get; set; } 254 | public string Operator { get; set; } 255 | public string Level { get; set; } 256 | /// 257 | /// 保存的文件夹,基础文件夹为项目所在位置下的/log,如果Dir为login,那么Dir=/log/login 258 | /// 259 | public string Dir { get; set; } 260 | } 261 | [Serializable] 262 | internal class LogBatch 263 | { 264 | public LogBatch() 265 | { 266 | LogStr = new StringBuilder(); 267 | } 268 | public string Dir { get; set; } 269 | public int Count { get; set; } 270 | public DateTime StartTime { get; set; } 271 | public StringBuilder LogStr { get; set; } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace LogService 12 | { 13 | class Program 14 | { 15 | static void Main(string[] args) 16 | { 17 | string dir= Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log/"); 18 | LogServiceHelper.Intance.Start(dir); 19 | LogWriteTest(); 20 | } 21 | static void LogWriteTest() 22 | { 23 | Stopwatch sw = new Stopwatch(); 24 | sw.Start(); 25 | for (int i = 0; i < 5; i++) 26 | { 27 | var _thread = new Thread(Start); 28 | _thread.IsBackground = true; 29 | _thread.Start(i); 30 | } 31 | while (LogServiceHelper.Intance.GetQueueCount() > 0) 32 | { 33 | 34 | } 35 | sw.Stop(); 36 | LogServiceHelper.Intance.Stop(); 37 | long s = sw.ElapsedMilliseconds / 1000; 38 | Console.WriteLine("共耗时:"+s+"秒"); 39 | Console.ReadLine(); 40 | 41 | } 42 | static void Start(object tag) 43 | { 44 | for (int i = 0; i < 200000; i++) 45 | { 46 | LogServiceHelper.Intance.Write("测试" + tag, i + "测试测试测试测试测试", "sa"); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LogService 2 | 基于C#的日志记录组件,该组件采用多线程写日志队列,单线程写日志文件的模式记录日志。平均每秒写入20万条记录。 3 | 4 | 5 | # 功能介绍 6 | * 1.多类型日志自动归类,将不同类型的日志写入不同的文件中。例如:登录日志,操作日志,错误日志等 7 | * 2.批量写入日志,减少IO操作 8 | * 3.延时写入功能,当日志缓存区大小小于某个阈值时不写入 9 | * 4.强制写入功能,当日志在批处理列表中保留时间超过某个阈值时强制写入 10 | * 5.日志分割(按天,按体积分割) 11 | * 6.报警机制,当写入日志出错,并且连续出错10次(可配置),可发送报警短信(需自己实现) 12 | 13 | 详情请参照:http://www.cnblogs.com/eggTwo/p/6394028.html 14 | 15 | 16 | 17 | # 测试代码如下 18 | 19 | static void Main(string[] args) 20 | { 21 | string dir= Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log/"); 22 | LogServiceHelper.Intance.Start(dir); 23 | LogWriteTest(); 24 | } 25 | static void LogWriteTest() 26 | { 27 | Stopwatch sw = new Stopwatch(); 28 | sw.Start(); 29 | for (int i = 0; i < 5; i++) 30 | { 31 | var _thread = new Thread(Start); 32 | _thread.IsBackground = true; 33 | _thread.Start(i); 34 | } 35 | while (LogServiceHelper.Intance.GetQueueCount() > 0) 36 | { 37 | 38 | } 39 | sw.Stop(); 40 | LogServiceHelper.Intance.Stop(); 41 | long s = sw.ElapsedMilliseconds / 1000; 42 | Console.WriteLine("共耗时:"+s+"秒"); 43 | Console.ReadLine(); 44 | 45 | } 46 | static void Start(object tag) 47 | { 48 | for (int i = 0; i < 200000; i++) 49 | { 50 | LogServiceHelper.Intance.Write("测试" + tag, i + "测试测试测试测试测试", "sa"); 51 | } 52 | } 53 | 54 | 55 | 56 | 57 | --------------------------------------------------------------------------------