2016-04-25 10 views
10

Ho un servizio TCP di Windows, che ha molti dispositivi che si connettono ad esso e un client può avere uno o più dispositivi.Separato file di registro e directory per ogni cliente e data

Requisito:

cartella separata per ogni cliente con file di registro separato per ogni dispositivo.

quindi qualcosa di simile:

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

    Client 2/ 
     Device1.txt 
     Device2.txt 
     Device3.txt 

Ora non ho usato una libreria di 3a parte come log4net o NLog, ho una classe che gestisce questo.

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(); 
    } 
} 

E poi posso usare in questo modo:

var _logger = new xPTLogger("DeviceId", 12); 

_logger.LogMessage(MessageType.Info, string.Format("Information Message = [{0}]", 1)); 

Il problema con la classe di cui sopra è che, perché il servizio è multi-threaded, alcuni fili tentano di accedere allo stesso file di registro allo stesso tempo causa un'eccezione a Throw.

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. 

Che a volte causa il blocco del servizio.

Come faccio a far funzionare la mia classe di Logger in servizi multi-thread?

EDIT

Modifiche al Logger Classe

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

'NLog' è sicuro. Puoi usarlo per servizi multithread. Vedere [questa risposta] (http://stackoverflow.com/a/5706633/579895) – Pikoh

+0

Hai provato a spostare il blocco 'lock (fileLocker)' per racchiudere l'intero contenuto del metodo LogToFile? Cioè per evitare che WriteAllText e AppendAllText vengano richiamati sullo stesso file da due thread diversi. – user469104

+0

@ user469104 No, proverò a provare –

risposta

5

Se non si desidera utilizzare le soluzioni esistenti, l'approccio ragionevole per gestire le scritture multithread nel logger è utilizzare la coda. Ecco uno schizzo:

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; } 
} 

Poi nella classe logger:

private void LogToFile(string logFilePath, string message) { 
    LogQueue.Instance.QueueItem(new LogItem() { 
     TargetFile = logFilePath, 
     Message = message 
    }); 
} 

Qui delegare la registrazione reale per classe separata che scrive i messaggi uno per uno il login, quindi non può avere alcuna problemi di multithreading. Un ulteriore vantaggio di tale approccio è che la registrazione avviene in modo asincrono e in quanto tale non rallenta il vero lavoro.

Lo svantaggio è che è possibile perdere alcuni messaggi in caso di arresto anomalo del processo (non pensare che sia davvero un problema, ma menzionarlo ancora) e si usi thread separati per registrare in modo asincrono.Quando c'è un thread non è un problema, ma se si crea un thread per dispositivo, ciò potrebbe essere (anche se non è necessario: basta usare una singola coda, a meno che non si scriva davvero MOLTO messaggio al secondo).

+0

ho implementato questo metodo un paio di giorni fa - ed ora è in produzione. Per quanto riguarda le prestazioni è buono - Avere una coda/thread per fare tutto il logging funziona bene, non ci sono più eccezioni di accesso ai file - Grazie –

3

Anche se probabilmente non è la soluzione più elegante, si potrebbe avere la logica di tentativi costruita nel Ad esempio:.

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++; 
    } 
} 

Inoltre, ho scritto quel codice solo ora senza testarlo, quindi se c'è qualche errore stupido in esso perdonami. Ma semplicemente cercherà di scrivere sul registro tutte le volte che si imposta sulla riga "while". Puoi anche aggiungere un'istruzione di sonno nel blocco catch se pensi che ne varrà la pena.

Non ho esperienza con Log4Net o NLog quindi nessun commento lì. Forse c'è una soluzione dolce tramite uno di quei pacchetti. In bocca al lupo!

+0

Oh, probabilmente sarebbe una buona idea mettere quel blocco di tentativi nel metodo LogMessage per minimizzare il codice. – bseyeph

+0

Ho dimenticato di menzionare nella mia domanda che a volte il servizio si blocca usando il mio xPTLogger, ecco perché sto cercando un'alternativa –

Problemi correlati