我有一个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}]", 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));
上述类的问题在于,由于服务是多线程的,因此某些线程会尝试同时访问同一个日志文件,从而导致抛出异常。
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类在多线程服务中工作?
修改
记录器类的更改
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;
}
}
答案 0 :(得分:5)
如果您不想使用现有解决方案,则在记录器中处理多线程写入的合理方法是使用队列。这是一幅草图:
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
});
}
这里我们将实际的日志记录委托给单独的类,它逐个写入日志消息,因此不会出现任何多线程问题。这种方法的另一个好处是日志记录异步发生,因此不会减慢实际工作。
缺点是你可以在进程崩溃的情况下丢失一些消息(不要认为这确实是一个问题但仍然提到它)并且你使用单独的线程来异步记录。当有一个线程时,这不是问题,但是如果你为每个设备创建一个线程,那可能是(尽管没有必要 - 只需使用单个队列,除非你真的每秒写了很多消息)。
答案 1 :(得分:3)
虽然它可能不是最优雅的解决方案,但您可以内置重试逻辑。例如:
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++;
}
}
另外,我刚刚编写了这段代码而没有实际测试它,所以如果它有一些愚蠢的错误,请原谅我。但很简单,它会尝试写入日志,就像你在&#34;中设置的那样多次。线。如果你觉得它值得,你甚至可以在catch块中添加一个sleep语句。
我没有使用Log4Net或NLog的经验,所以没有评论。也许通过其中一个软件包可以获得甜蜜的解决方案。祝你好运!