2013-06-05 15 views
7

L'applicazione My C# winforms 4.0 utilizza uno streamwriter thread-safe per eseguire le informazioni di registrazione interne e di debug. Quando si apre la mia app, cancella il file e lo ricrea. Quando l'app si chiude, salva il file.Utilizzo di StreamWriter per implementare un registro a rotazione e eliminazione dall'alto

Quello che mi piacerebbe fare è modificare la mia applicazione in modo che appenda invece di sostituire. Questa è una soluzione semplice.

Tuttavia, ecco la mia domanda:

vorrei mantenere il mio file di log circa 10 megabyte massimo. Il mio vincolo sarebbe semplice. Quando si va a chiudere il file, se il file è superiore a 10 megabyte, ritagliare il primo 10%.

Esiste un modo 'migliore' quindi nel seguente modo:

  1. Chiudere il file
  2. Controllare se il file è> 10 meg
  3. In tal caso, aprire il file
  4. analizzare il intera cosa
  5. Cull il primo 10%
  6. scrivere il file di nuovo fuori
  7. Chiudi

Edit: bene, ho finito per rotazione mia (mostrato in seguito) il suggerimento di passare palese a Log4Net è una buona, ma il tempo che merion vuole per imparare la nuova libreria e spostare tutte le mie dichiarazioni di registro (migliaia) non è il momento efficace per il piccolo miglioramento che stavo cercando di fare.

private static void PerformFileTrim(string filename) 
    { 
    var FileSize = Convert.ToDecimal((new System.IO.FileInfo(filename)).Length); 

    if (FileSize > 5000000) 
    { 
     var file = File.ReadAllLines(filename).ToList(); 
     var AmountToCull = (int)(file.Count * 0.33); 
     var trimmed = file.Skip(AmountToCull).ToList(); 
     File.WriteAllLines(filename, trimmed); 
    } 
    } 
+5

E che ne è dell'utilizzo di una specifica libreria scritta per la registrazione che già implementa questa funzionalità? (Mi viene in mente Log4Net) – Steve

+0

Non ho avuto il tempo di guardare in Log4Net. Fino ad ora, la mia semplice lezione di streamwriter è andata benissimo. Se Log4Net consente il log "rolling" che sto cercando, è sicuramente la soluzione che dovrei usare. – greggorob64

+0

I futuri lettori: "" ... [to] spostare tutte le mie istruzioni di registro (migliaia) non è efficace nel tempo "' - ed è per questo che ci affidiamo all'iniezione e alle interfacce di dipendenza. :) Un design migliore, ad esempio l'iniezione di un'istanza di "ILogger' (ad esempio) per tutte le classi che necessitano di registrazione - o anche solo una singola classe/funzione di registrazione master - consentirebbe di modificare tutte le funzionalità di registrazione in una posizione quando i requisiti di registrazione cambiano . – brichins

risposta

1

stavo guardando attraverso l'API Win32, e io non sono nemmeno sicuro che sia possibile farlo con le chiamate Win32 VFS nativi, non importa attraverso .Net.

L'unica soluzione sarebbe utilizzare i file mappati in memoria e spostare manualmente i dati, che .Net sembra supportare da .Net 4.0.

Memory Mapped Files

+0

file mappati in memoria è un overkill per file 10mb – VladL

6

ho ricercato una volta e non è mai venuto in mente nulla, ma può offrirvi piano B qui:

uso la lista qui sotto per mantenere un massimo di 3 file di log. Inizialmente, il file di registro 1 viene creato e aggiunto a. Quando supera il valore massimo, vengono creati il ​​registro 2 e il successivo registro 3. Quando il registro 3 è troppo grande, il registro 1 viene eliminato e i registri rimanenti vengono spostati nella pila.

string[] logFileList = Directory.GetFiles(Path.GetTempPath(), "add_all_*.log", SearchOption.TopDirectoryOnly); 
if (logFileList.Count() > 1) 
{ 
    Array.Sort(logFileList, 0, logFileList.Count()); 
} 

if (logFileList.Any()) 
{ 
    string currFilePath = logFileList.Last(); 
    string[] dotSplit = currFilePath.Split('.'); 
    string lastChars = dotSplit[0].Substring(dotSplit[0].Length - 3); 
    ctr = Int32.Parse(lastChars); 
    FileInfo f = new FileInfo(currFilePath); 

    if (f.Length > MaxLogSize) 
    { 
     if (logFileList.Count() > MaxLogCount) 
     { 
      File.Delete(logFileList[0]); 
      for (int i = 1; i < MaxLogCount + 1; i++) 
      { 
       Debug.WriteLine(string.Format("moving: {0} {1}", logFileList[i], logFileList[i - 1])); 
       File.Move(logFileList[i], logFileList[i - 1]); // push older log files back, in order to pop new log on top 
      } 
     } 
     else 
     { 
      ctr++; 
     } 
    } 
} 
+0

Ho dovuto apportare alcune modifiche per far sì che questo venga eseguito per me - l'ho aggiunto come risposta separata a causa del vincolo di dimensione ... – user3902302

2

Questo deriva dalla risposta di bigtech:

private static string RollLogFile() 
    { 
     string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 
     string appName = Path.GetFileNameWithoutExtension(Environment.GetCommandLineArgs()[0]); 
     string wildLogName = string.Format("{0}*.log",appName); 

     int fileCounter = 0; 
     string[] logFileList = Directory.GetFiles(path, wildLogName, SearchOption.TopDirectoryOnly); 
     if (logFileList.Length > 0) 
     { 
      Array.Sort(logFileList, 0, logFileList.Length); 
      fileCounter = logFileList.Length - 1; 
      //Make sure we apply the MaxLogCount (but only once to reduce the delay) 
      if (logFileList.Length > MaxLogCount) 
      { 
       //Too many files - remove one and rename the others 
       File.Delete(logFileList[0]); 
       for (int i = 1; i < logFileList.Length; i++) 
       { 
        File.Move(logFileList[i], logFileList[i - 1]); 
       } 
       --fileCounter; 
      } 

      string currFilePath = logFileList[fileCounter]; 
      FileInfo f = new FileInfo(currFilePath); 
      if (f.Length < MaxLogSize) 
      { 
       //still room in the current file 
       return currFilePath; 
      } 
      else 
      { 
       //need another filename 
       ++fileCounter;     
      } 

     } 
     return string.Format("{0}{1}{2}{3:00}.log", path, Path.DirectorySeparatorChar, appName, fileCounter); 
    } 

Usage:

string logFileName = RollLogFile(); 
using (StreamWriter sw = new StreamWriter(logFileName, true)) 
{ 
    sw.AutoFlush = true; 
    sw.WriteLine(string.Format("{0:u} {1}", DateTime.Now, message)); 
} 
2

le soluzioni qui non funzionava per me. Ho preso la risposta di user3902302, che ancora una volta era basata sulla risposta di bigtech e ha scritto una lezione completa. Inoltre, NON sto usando StreamWriter, puoi cambiare la riga (AppendAllText contro StreamWrite aequivalent).

C'è poca gestione degli errori (ad esempio riprova l'accesso quando non è in grado, anche se il blocco deve catturare tutti gli accessi interni concomitanti).

Questo potrebbe essere sufficiente per alcune persone che hanno dovuto utilizzare una soluzione di grandi dimensioni come log4net o nlog prima. (E log4net RollingAppender non è nemmeno thread-safe, questo è :).)

public class RollingLogger 
{ 
    readonly static string LOG_FILE = @"c:\temp\logfile.log"; 
    readonly static int MaxRolledLogCount = 3; 
    readonly static int MaxLogSize = 1024; // 1 * 1024 * 1024; <- small value for testing that it works, you can try yourself, and then use a reasonable size, like 1M-10M 

    public static void LogMessage(string msg) 
    { 
     lock (LOG_FILE) // lock is optional, but.. should this ever be called by multiple threads, it is safer 
     { 
      RollLogFile(LOG_FILE); 
      File.AppendAllText(LOG_FILE, msg + Environment.NewLine, Encoding.UTF8); 
     } 
    } 

    private static void RollLogFile(string logFilePath) 
    { 
     try 
     { 
      var length = new FileInfo(logFilePath).Length; 

      if (length > MaxLogSize) 
      { 
       var path = Path.GetDirectoryName(logFilePath); 
       var wildLogName = Path.GetFileNameWithoutExtension(logFilePath) + "*" + Path.GetExtension(logFilePath); 
       var bareLogFilePath = Path.Combine(path, Path.GetFileNameWithoutExtension(logFilePath)); 
       string[] logFileList = Directory.GetFiles(path, wildLogName, SearchOption.TopDirectoryOnly); 
       if (logFileList.Length > 0) 
       { 
        // only take files like logfilename.log and logfilename.0.log, so there also can be a maximum of 10 additional rolled files (0..9) 
        var rolledLogFileList = logFileList.Where(fileName => fileName.Length == (logFilePath.Length + 2)).ToArray(); 
        Array.Sort(rolledLogFileList, 0, rolledLogFileList.Length); 
        if (rolledLogFileList.Length >= MaxRolledLogCount) 
        { 
         File.Delete(rolledLogFileList[MaxRolledLogCount - 1]); 
         var list = rolledLogFileList.ToList(); 
         list.RemoveAt(MaxRolledLogCount - 1); 
         rolledLogFileList = list.ToArray(); 
        } 
        // move remaining rolled files 
        for (int i = rolledLogFileList.Length; i > 0; --i) 
         File.Move(rolledLogFileList[i - 1], bareLogFilePath + "." + i + Path.GetExtension(logFilePath)); 
        var targetPath = bareLogFilePath + ".0" + Path.GetExtension(logFilePath); 
        // move original file 
        File.Move(logFilePath, targetPath); 
       } 
      } 
     } 
     catch (Exception ex) 
     { 
      System.Diagnostics.Debug.WriteLine(ex.ToString()); 
     } 
    } 
} 

edit:
Dal momento che ho appena notato che lei ha chiesto una domanda leggermente diversa: se il vostro linee variare notevolmente in termini di dimensioni , questa sarebbe una variazione (, ma nel 90% dei casi non migliora la tua, anche se potrebbe essere leggermente più veloce, ha anche introdotto un nuovo errore non gestito (\ n non è presente):

private static void PerformFileTrim(string filename) 
    { 
     var fileSize = (new System.IO.FileInfo(filename)).Length; 

     if (fileSize > 5000000) 
     { 
      var text = File.ReadAllText(filename); 
      var amountToCull = (int)(text.Length * 0.33); 
      amountToCull = text.IndexOf('\n', amountToCull); 
      var trimmedText = text.Substring(amountToCull + 1); 
      File.WriteAllText(filename, trimmedText); 
     } 
    } 
2

Questa funzione consente di ruotare il registro in base ai giorni feriali. La prima volta che la nostra applicazione verrà avviata lunedì, verificherà la presenza di una voce esistente per la data del lunedì, se non già inizializzata per oggi eliminerà le voci precedenti e reinizializzerà il nuovo file. In poi per tutto il giorno, il file continuerà ad aggiungere il testo allo stesso file di registro.

Quindi, verranno creati 7 file di registro. debug-Mon.txt, debog-Tue.txt ...

aggiungerà anche il nome del metodo che ha effettivamente registrato il messaggio insieme alla data. molto utile per uso generale.

private void log(string text) 
     { 
      string dd = DateTime.Now.ToString("yyyy-MM-dd"); 
      string mm = DateTime.Now.ToString("ddd"); 

      if (File.Exists("debug-" + mm + ".txt")) 
      { 
       String contents = File.ReadAllText("debug-" + mm + ".txt"); 


       if (!contents.Contains("Date: " + dd)) 
       { 
        File.Delete("debug-" + mm + ".txt"); 
       } 
      } 

      File.AppendAllText("debug-" + mm + ".txt", "\r\nDate: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:s") + " =>\t" + new System.Diagnostics.StackFrame(1, true).GetMethod().Name + "\t" + text); 

     }