我有一个
Windows TCP服务,它有许多设备连接到它,客户端可以有一个或多个设备.
需求:
所以这样的东西:
/MyService/25-04-2016/ Client 1/ Device1.txt Device2.txt Device3.txt Client 2/ Device1.txt Device2.txt Device3.txt
现在我没有使用第三方库,如log4net或NLog,我有一个处理这个的类.
public class xPTLogger : IDisposable { private static object fileLocker = new object(); private readonly string _logFileName; private readonly string _logFilesLocation; private readonly int _clientId; public xPTLogger() : this("General") { } public xPTLogger(string logFileName) { _clientId = -1; _logFileName = logFileName; _logFilesLocation = SharedConstants.LogFilesLocation; // D:/LogFiles/ } public xPTLogger(string logFileName,int companyId) { _clientId = companyId; _logFileName = logFileName; _logFilesLocation = SharedConstants.LogFilesLocation; } public void LogMessage(MessageType messageType,string message) { LogMessage(messageType,message,_logFileName); } public void LogExceptionMessage(string message,Exception innerException,string stackTrace) { var exceptionMessage = innerException != null ? string.Format("Exception: [{0}],Inner: [{1}],Stack Trace: [{2}]",innerException.Message,stackTrace) : string.Format("Exception: [{0}],Stack Trace: [{1}]",stackTrace); LogMessage(MessageType.Error,exceptionMessage,"Exceptions"); } public void LogMessage(MessageType messageType,string message,string logFileName) { var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy"); var logFilesLocation = string.Format("{0}{1}\\",_logFilesLocation,dateTime); if (_clientId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\",dateTime,_clientId); } var fullLogFile = string.IsNullOrEmpty(logFileName) ? "GeneralLog.txt" : string.Format("{0}.txt",logFileName); var msg = string.Format("{0} | {1} | {2}\r\n",DateTime.UtcNow.ToString("dd-MMM-yyyy HH:mm:ss"),messageType,message); fullLogFile = GenerateLogFilePath(logFilesLocation,fullLogFile); LogToFile(fullLogFile,msg); } private string GenerateLogFilePath(string objectLogDirectory,string objectLogFileName) { if (string.IsNullOrEmpty(objectLogDirectory)) throw new ArgumentNullException(string.Format("{0} location cannot be null or empty","objectLogDirectory")); if (string.IsNullOrEmpty(objectLogFileName)) throw new ArgumentNullException(string.Format("{0} cannot be null or empty","objectLogFileName")); if (!Directory.Exists(objectLogDirectory)) Directory.CreateDirectory(objectLogDirectory); string logFilePath = string.Format("{0}\\{1}",objectLogDirectory,objectLogFileName); return logFilePath; } private void LogToFile(string logFilePath,string message) { if (!File.Exists(logFilePath)) { File.WriteAllText(logFilePath,message); } else { lock (fileLocker) { File.AppendAllText(logFilePath,message); } } } public void Dispose() { fileLocker = new object(); } }
然后我可以这样使用它:
var _logger = new xPTLogger("DeviceId",12); _logger.LogMessage(MessageType.Info,string.Format("Information Message = [{0}]",1));
上述类的问题是,由于服务是多线程的,一些线程尝试访问相同的日志文件,同时导致异常抛出.
25-Apr-2016 13:07:00 | Error | Exception: The process cannot access the file 'D:\LogFiles\25-Apr-2016\0\LogFile.txt' because it is being used by another process.
这有时导致我的服务崩溃.
如何使我的Logger类工作在多线程服务中?
编辑
对Logger类的更改
public class xPTLogger : IDisposable { private object fileLocker = new object(); private readonly string _logFileName; private readonly string _logFilesLocation; private readonly int _companyId; public xPTLogger() : this("General") { } public xPTLogger(string logFileName) { _companyId = -1; _logFileName = logFileName; _logFilesLocation = SharedConstants.LogFilesLocation; // "D:\\MyLogs"; } public xPTLogger(string logFileName,int companyId) { _companyId = companyId; _logFileName = logFileName; _logFilesLocation = SharedConstants.LogFilesLocation; } public void LogMessage(MessageType messageType,string logFileName) { if (messageType == MessageType.Debug) { if (!SharedConstants.EnableDebugLog) return; } var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy"); var logFilesLocation = string.Format("{0}{1}\\",dateTime); if (_companyId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\",_companyId); } var fullLogFile = string.IsNullOrEmpty(logFileName) ? "GeneralLog.txt" : string.Format("{0}.txt",string message) { lock (fileLocker) { try { if (!File.Exists(logFilePath)) { File.WriteAllText(logFilePath,message); } else { File.AppendAllText(logFilePath,message); } } catch (Exception ex) { var exceptionMessage = ex.InnerException != null ? string.Format("Exception: [{0}],ex.Message,ex.InnerException.Message,ex.StackTrace) : string.Format("Exception: [{0}],ex.StackTrace); var logFilesLocation = string.Format("{0}{1}\\",DateTime.UtcNow.ToString("dd-MMM-yyyy")); var logFile = GenerateLogFilePath(logFilesLocation,"FileAccessExceptions.txt"); try { if (!File.Exists(logFile)) { File.WriteAllText(logFile,exceptionMessage); } else { File.AppendAllText(logFile,exceptionMessage); } } catch (Exception) { } } } } public void Dispose() { //fileLocker = new object(); //_logFileName = null; //_logFilesLocation = null; //_companyId = null; } }
解决方法
如果不想使用现有的解决方案,在记录器中处理多线程写入的合理方法是使用队列.这是草图:
public class LogQueue : IDisposable { private static readonly Lazy<LogQueue> _isntance = new Lazy<LogQueue>(CreateInstance,true); private Thread _thread; private readonly BlockingCollection<LogItem> _queue = new BlockingCollection<LogItem>(new ConcurrentQueue<LogItem>()); private static LogQueue CreateInstance() { var queue = new LogQueue(); queue.Start(); return queue; } public static LogQueue Instance => _isntance.Value; public void QueueItem(LogItem item) { _queue.Add(item); } public void Dispose() { _queue.CompleteAdding(); // wait here until all pending messages are written _thread.Join(); } private void Start() { _thread = new Thread(ConsumeQueue) { IsBackground = true }; _thread.Start(); } private void ConsumeQueue() { foreach (var item in _queue.GetConsumingEnumerable()) { try { // append to your item.TargetFile here } catch (Exception ex) { // do something or ignore } } } } public class LogItem { public string TargetFile { get; set; } public string Message { get; set; } public MessageType MessageType { get; set; } }
然后在你的记录器类中:
private void LogToFile(string logFilePath,string message) { LogQueue.Instance.QueueItem(new LogItem() { TargetFile = logFilePath,Message = message }); }
在这里,我们将实际的日志记录委托给一个逐个写入日志的单独的类,所以不能有任何多线程问题.这种方法的另外的好处是记录异步发生,因此不会减慢实际工作.
缺点是在进程崩溃的情况下可能会丢失一些消息(不要以为这是真的是一个问题,但仍然提到它),并且您消耗单独的线程以异步记录.当有一个线程时,它不是一个问题,但是如果您为每个设备创建一个线程,那可能是(尽管不需要 – 只需使用单个队列,除非您每秒写入大量消息).