为每个客户端和日期分隔日志文件和目录

我有一个Windows TCP服务,有许多设备连接到它,客户端可以有一个或多个设备。

需求:

每个客户端单独的文件夹,每个设备都有单独的日志文件。

所以这样的事情:

/MyService/25-04-2016/ Client 1/ Device1.txt Device2.txt Device3.txt Client 2/ Device1.txt Device2.txt Device3.txt 

现在我还没有使用像log4netNLog这样的第三方库,我有一个处理这个问题的类。

 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}]", message, innerException.Message, stackTrace) : string.Format("Exception: [{0}], Stack Trace: [{1}]", message, 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}\\", _logFilesLocation, 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)); 

上述类的问题在于,由于服务是multithreading的,因此某些线程会尝试同时访问同一个日志文件,从而导致抛出exception。

 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类在multithreading服务中工作?

编辑

记录器类的更改

 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 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}]", message, innerException.Message, stackTrace) : string.Format("Exception: [{0}], Stack Trace: [{1}]", message, stackTrace); LogMessage(MessageType.Error, exceptionMessage, "Exceptions"); } public void LogMessage(MessageType messageType, string message, string logFileName) { if (messageType == MessageType.Debug) { if (!SharedConstants.EnableDebugLog) return; } var dateTime = DateTime.UtcNow.ToString("dd-MMM-yyyy"); var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, dateTime); if (_companyId > -1) { logFilesLocation = string.Format("{0}{1}\\{2}\\", _logFilesLocation, dateTime, _companyId); } 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) { 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}], Inner: [{1}], Stack Trace: [{2}]", ex.Message, ex.InnerException.Message, ex.StackTrace) : string.Format("Exception: [{0}], Stack Trace: [{1}]", ex.Message, ex.StackTrace); var logFilesLocation = string.Format("{0}{1}\\", _logFilesLocation, 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; } } 

如果您不想使用现有解决方案,则在记录器中处理multithreading写入的合理方法是使用队列。 这是一个草图:

 public class LogQueue : IDisposable { private static readonly Lazy _isntance = new Lazy(CreateInstance, true); private Thread _thread; private readonly BlockingCollection _queue = new BlockingCollection(new ConcurrentQueue()); 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 }); } 

在这里,我们将实际日志记录委托给单独的类,该类逐个写入日志消息,因此不会出现任何multithreading问题。 这种方法的另一个好处是,日志记录是异步发生的,因此不会减慢实际工作的速度。

缺点是你可以在进程崩溃的情况下丢失一些消息(不要认为这确实是一个问题但仍然提到它)并且你使用单独的线程来异步记录。 当有一个线程时,这不是问题,但如果你为每个设备创建一个线程,那可能是(虽然没有必要 – 只需使用单个队列,除非你真的每秒写一大堆消息)。

虽然它可能不是最优雅的解决方案,但您可以内置重试逻辑。例如:

 int retries = 0; while(retries <= 3){ try{ var _logger = new xPTLogger("DeviceId", 12); _logger.LogMessage(MessageType.Info, string.Format("Information Message = [{0}]", 1)); break; } catch (Exception ex){ //Console.WriteLine(ex.Message); retries++; } } 

此外,我刚刚编写了该代码而没有实际测试它,所以如果有一些愚蠢的错误,请原谅我。 但很简单,它会尝试在“while”行中设置多次写入日志。 如果你觉得它值得,你甚至可以在catch块中添加一个sleep语句。

我没有Log4Net或NLog的经验所以没有评论。 也许通过其中一个软件包有一个很好的解决方案。 祝好运!