Sehr große Log-Dateien verwalten

  • VB.NET

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von petaod.

    Sehr große Log-Dateien verwalten

    Hallo zusammen,

    ich versuche im Moment ein Programm zu schreiben, das Log-Dateien analysieren kann.
    Ein Problem das dabei auftritt, ist die Größe der einzelnen Dateien die stark schwanken kann.
    Die Dateien können quasi unendlich groß werden und da hab ich auch leider keinen Einfluss drauf.
    Da es sehr sinnlos ist solch eine Datenmenge von Hand zu durchsuchen, kommt mein Tool ins Spiel.
    Aber allein wenn ich die Datei laden will, kommt eine out of memory exaption.
    Ich kenne einige Editoren, die solch eine Datenmenge sehr schnell anzeigen können, deswegen ist mein erstes Ziel die Datei irgendwie anzuzeigen.

    Weiß da jemand Rat?

    Gruß,
    Hitch
    Sind diese Log-Dateien zeilenbasiert aufgebaut?
    Sind diese Log-Dateien so aufgebaut, dass da das Lesen einer Zeile zur Analyse ausreicht
    oder
    muss eine Gruppe von Zeilen gelesen werden, um einen Log-Fall gelesen zu haben?
    Wieviel dieser Information soll / muss angezeigt werden?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @ AliveDevil
    Das mit dem System.IO.StreamReader habe ich versucht, wird auch “relative” Zeitnah Zeile für Zeile geladen jedoch dauert das mit einer jeweiligen Ausgabe jeder Zeile ewig.

    @ RodFromGermany
    Das ist ganz unterschiedlich, aber die meisten sind in Gruppen aufgebaut.
    Zuerst möchte ich einmal alles aufgeben z.B. in einer Richtextbox (obwohl ich nicht glaube, dass das geht).

    Wie oben schon erwähnt gibt es Editoren die diese Dateien in 1 – 2 sek. öffnen können.
    Wie machen die das denn?

    Hitch schrieb:

    Wie machen die das denn?
    Ich glaube nicht, dass diese Editoren den gesamten Inhalt gelesen haben, sondern nur ein (großes) Stück der Datei.
    Wenn die merken, dass mehr angefordert wird, wird halt nachgelegt.
    Da steckt die Intelligenz im Einlesen und in der Speicherverwaltung.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Also mit meinem Programm, was ich mir eben geschrieben habe, kann ich ne Datei mit 134951 Einträgen bestens einlesen.
    Ich nutze dafür WPF mit dem .NET 4.5 (kann allerdings auch mit dem .NET 4 durchgeführt werden) und das MVVM Light Toolkit.

    MainViewModel.cs

    C-Quellcode

    1. namespace FileRead.ViewModel {
    2. public class MainViewModel : ViewModelBase {
    3. // um den Status anzeigen zu lassen.
    4. private string status;
    5. public string Status { get { return status; } set { status = value; this.RaisePropertyChanged( "Status" ); } }
    6. // einfach nur Zufall :D
    7. Random rnd;
    8. // damit eine Liste existiert, die mit WPF kompatibel ist.
    9. ObservableCollection<LogViewModel> logs = new ObservableCollection<LogViewModel>();
    10. // wichtig für das DataGrid!
    11. CollectionViewSource logView;
    12. public CollectionViewSource Logs { get { return logView; } }
    13. // ein Command, der von einem Button gestartet ist.
    14. private RelayCommand cDummyFile;
    15. public RelayCommand CDummyFile {
    16. get {
    17. // wenn cDummyFile != null ist, erzeuge ein neues Objekt
    18. return cDummyFile ?? ( cDummyFile = new RelayCommand( () => {
    19. // eine Dummy-Datei erstellen
    20. FileInfo fI = new FileInfo( "dummy.file" );
    21. if ( fI.Exists )
    22. fI.Delete();
    23. // und beschreiben.
    24. using ( FileStream fS = fI.OpenWrite() ) {
    25. using ( StreamWriter strW = new StreamWriter( fS ) ) {
    26. strW.AutoFlush = true;
    27. // Random * 1000 Einträge schreiben.
    28. for ( ulong i = 0; i < (ulong) ( rnd.Next() * 1000 ); i++ ) {
    29. // das mit einem Base64 in die Datei schreiben.
    30. strW.WriteLine( string.Format( "{0} | {1}", i, Convert.ToBase64String( BitConverter.GetBytes( i << rnd.Next( 5 ) ) ) ) );
    31. }
    32. }
    33. }
    34. } ) );
    35. }
    36. }
    37. private RelayCommand rDummyFile;
    38. public RelayCommand RDummyFile {
    39. get {
    40. return rDummyFile ?? ( rDummyFile = new RelayCommand( () => {
    41. FileInfo fI = new FileInfo( "dummy.file" );
    42. if ( !fI.Exists )
    43. return;
    44. // den Log löschen.
    45. logs.Clear();
    46. // Zeitmessung!
    47. Stopwatch sW = new Stopwatch();
    48. sW.Start();
    49. // Datei zum Lesen öffnen
    50. using ( FileStream fS = fI.OpenRead() ) {
    51. // den StreamReader öffnen.
    52. using ( StreamReader strR = new StreamReader( fS ) ) {
    53. // solange nicht das Ende erreicht wurde
    54. while ( !strR.EndOfStream ) {
    55. string line = strR.ReadLine();
    56. // Tuples sollte man getrost vergessen. Korrekt wäre hier:
    57. // LogViewModel tLine = new LogViewModel() { Time = Convert.ToUInt64( line.Slit( '|' )[0],
    58. // line.Split( '|' )[1] };
    59. // und damit dann logs.Add( tLine );
    60. Tuple<int, string> lines = Tuple.Create( Convert.ToInt32( line.Split( '|' )[0] ), line.Split( '|' )[1] );
    61. logs.Add( new LogViewModel() { Time = lines.Item1, Line = lines.Item2 } );
    62. }
    63. }
    64. }
    65. sW.Stop();
    66. // Status ausgeben.
    67. Status = "Time needed: " + sW.Elapsed.ToString();
    68. } ) );
    69. }
    70. }
    71. // initialisierungen.
    72. public MainViewModel() {
    73. logView = new CollectionViewSource();
    74. logView.Source = logs;
    75. rnd = new Random();
    76. }
    77. }
    78. public class LogViewModel {
    79. public long Time { get; set; }
    80. public string Line { get; set; }
    81. }
    82. }


    MainWindow.xaml

    XML-Quellcode

    1. <Window x:Class="FileRead.MainWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. Title="MainWindow" Height="350" Width="525" DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
    5. <Grid>
    6. <Grid.RowDefinitions>
    7. <RowDefinition Height="20" />
    8. <RowDefinition Height="*" />
    9. </Grid.RowDefinitions>
    10. <Menu Grid.Row="0">
    11. <MenuItem Header="Create Dummy File" Command="{Binding Path=CDummyFile}" />
    12. <MenuItem Header="Read Dummy File" Command="{Binding Path=RDummyFile}" />
    13. <MenuItem Header="{Binding Path="Status"}" />
    14. </Menu>
    15. <DataGrid Grid.Row="1" AlternationCount="2" AlternatingRowBackground="LightGray" AutoGenerateColumns="True" CanUserAddRows="False" CanUserDeleteRows="False" ItemsSource="{Binding Path=Logs.View}" />
    16. </Grid>
    17. </Window>


    Ist zwar C#, allerdings sollte es nicht so schwer sein, dies auch auf VB.NET umzuschreiben. Zum WPF Teil muss ich glaube ich nichts sagen, da es mMn. relativ verständlich ist.

    Einige Sachen sind nicht enthalten, da das MVVM Toolkit diese hinzufügt. Mit dem Tool bekomme ich ungefähr 1.56 Sekunden zum Lesen von, wie gesagt, knapp 135k Zeilen.

    Hitch schrieb:

    Wie oben schon erwähnt gibt es Editoren die diese Dateien in 1 – 2 sek. öffnen können. Wie machen die das denn?

    Zunächst einmal ist der Hinweis von AliceDevil vollkommen richtig: Filestream und Streamreader solltest Du Dir definitiv ansehen.

    Ein Log-File von Anfang bis Ende zu analysieren dauert nun eben seine Zeit.

    Bei aufeinanderfolgenden Analysen geht es allerdings leichter , wenn Du direkt an dem Punkt (Zeile/Gruppe) aufsetzt, wo Du das letzte Mal aufgehört hast: es werden nur die 'neuen' Log-Einträge analysiert.

    Dazu bietet die FileStream Klasse die Filestream.Seek Methode an.

    Hitch schrieb:

    Zuerst möchte ich einmal alles aufgeben z.B. in einer Richtextbox (obwohl ich nicht glaube, dass das geht).
    Das geht wohl zweifellos, nur ist die RichTextbox ein grottenlahmes Control. Mag durchaus sein , daß Dein Zeitaufwand rein durch dieses verursacht wird.

    Alternativen wäre die Textbox, das Datagridview oder externe Libraries wie FastColoredTextbox oder Scintilla.

    Zunächst einmal solltest Du allerdings wissen wo Deine Zeitverluste überhaupt herkommen: hast Du überhaupt schon einmal mit einer Stopwatch ein Profiling Deiner Auswertzeiten gemacht ?

    Wenn das feststeht, wäre der Code den Du für diesen Teil benutzt ebenfalls interessant ...

    Überhaupt: über welche File Grössen/Zeilen/Zeiten reden wir hier überhaupt ?
    @ AliveDevil
    Das werde ich mir heute abend mal genauer ansehen

    @ Kangaroo
    Ich habe hier eine Log-Datei von 1,75 GB mit 18274098 Zeilen
    Lese ich diese mit folgendem Code Zeile für Zeile, dauert das nur wenige sekunden (ca. 10 - 20).

    VB.NET-Quellcode

    1. Try
    2. ' Create an instance of StreamReader to read from a file.
    3. Dim sr As StreamReader = New StreamReader("C:\TEST.log")
    4. Dim line As String
    5. Dim Zaehler
    6. ' Read and display the lines from the file until the end
    7. ' of the file is reached.
    8. Do
    9. Zaehler += 1
    10. line = sr.ReadLine()
    11. Loop Until line Is Nothing
    12. sr.Close()
    13. Catch E As Exception
    14. ' Error MSG
    15. End Try
    Lass das try..Catch weg, Du willst wissen wenn Fehler auftreten: MemoryExceptions sollten beim zeilenweise Auslesen eh nicht auftreten.

    Vom Code und vom Zeitaufwand ist es OK, wo soll jetzt bitte noch gross optimiert werden ? Ein grobes Profiling geht wie gesagt über die Stopwatch-Klasse:

    VB.NET-Quellcode

    1. Dim line As String, count As Integer = 0
    2. Dim watch As New Stopwatch : watch.Start()
    3. Using sr As New StreamReader("C:\TEST.log")
    4. While Not sr.EndOfStream
    5. count += 1
    6. line = sr.ReadLine
    7. End While
    8. End Using
    9. watch.Stop() : Debug.Print("Reading {0:N0} lines in {0:N0}ms", count, watch.ElapsedMilliseconds)

    Hitch schrieb:

    in der Variablen line lieg jetzt jeweils immer eine Zeile ab. Wie und am Besten Wo kann ich diese jetzt alle ausgeben?
    18 mio Zeilen möchtest Du wirklich nirgendwo anzeigen :whistling:

    Erst einmal
    - bekommst Du dann wirklich Deine Memory Exception
    - keiner möchte die wirklich lesen
    Eher wirst Du wohl bei Deiner Analyse einen Filter definieren , der Dir zum Beispiel nur die Error Zeilen anzeigt oder Zeilen mit bestimmten von Dir definierten Suchbegriffen.

    Wenn ich es noch recht im Kopf habe so braucht die Richtextbox für 1.000 Zeilen ca 1 Sekunde ohne besondere Formatierung, bei den anderen genannte Alternativen sollte es eher etwas schneller gehen.

    Wie oben erwähnt kannst Du den Zeitaufwand für die Analysen drastisch verringern, wenn Du nur die seit dem letzten Mal 'neu hinzugekommenen' Logeinträge analysiert.
    in der Variablen line lieg jetzt jeweils immer eine Zeile ab. Wie und am Besten Wo kann ich diese jetzt alle ausgeben?


    Wozu ausgeben? Ich denke du willst die Datei analysieren.
    Also: Zeilenweise analysieren und dann das Ergebnis der Analyse ausgeben.

    Ein (kuzes) Beispiel deiner LOG-Datei und eine Beschreibung, wonach du 'analysieren' willst, wäre nicht verkehrt.
    Also die Log-Dateien sehen wie folgt aus:

    Quellcode

    1. E: 13/01/13 21:20:35 [SRV] Error while calculating favorite "test" for user test
    2. Process type ()
    3. Error while calculating favorite "test" for user test
    4. Process type ()
    5. .
    6. .
    7. .

    Wie gesagt die einzelnen Fehler sind in Gruppen dargestellt unter dem Beispiel "Process type ()" kann noch einiges mehr stehen.

    Mein Programm soll nun einen Tag ein einer neuen Log-Datei speichern:

    VB.NET-Quellcode

    1. Dim line As String, count As Integer = 0
    2. Dim watch As New Stopwatch : watch.Start()
    3. Dim datum As String = "13/01/13"
    4. Dim neuelog As String
    5. Dim zeilebearbeiten
    6. Using sr As New StreamReader("C:\TEST.log")
    7. While Not sr.EndOfStream
    8. count += 1
    9. line = sr.ReadLine
    10. If line.StartsWith("E:") Or line.StartsWith("I:") Or line.StartsWith("W:") Or line.StartsWith("F:") Then
    11. zeilebearbeiten = line
    12. zeilebearbeiten = zeilebearbeiten.ToString.Substring(3, 8)
    13. If zeilebearbeiten <> datum Then
    14. neuelog = neuelog & vbCrLf & line
    15. Exit While
    16. End If
    17. End If
    18. neuelog = neuelog & vbCrLf & line
    19. End While
    20. End Using
    21. watch.Stop() : Debug.Print("Reading {0:N0} lines in {0:N0}ms", count, watch.ElapsedMilliseconds)


    Ich vermute, dass das Speichern der Zeile in der Variablen "neuelog" der Flaschenhals ist.
    Ja, das bedeutet, dass die neueLog komplett im Speicher liegen musst. Außerdem arbeitest du mit String-Methoden - bei einer solchen größenordung kann es sicher sinnvoll sein den StringBuffer zu verwenden (funktioniert afaik genauso wie String, nur schneller - zumindest hat man uns das in Java eingetrichtert, getestet habe ich es nicht).

    Viel wichtiger: Du bearbeitest die Datei Zeile für Zeile - also kannst du relevante Informationen auch Zeile für Zeile in eine andere Datei schreiben. Statt StreamReader einfach einen StreamWriter benutzen um der neueLogFile eine Zeile hinzuzufügen - und erst ganz am Ende .flush aufrufen, so entsteht nur für jede n-te Zeile ein tatsächlicher Schreibvorgang und es sollte sehr schnell sein.

    lg
    Wenn ich dich richtig verstehe, möchtest du eine Log-Datei überwachen.
    Das was neu dazu kommt, möchtest du auswerten.
    Schau dir mal die tail-Funktion hier an:
    codeproject.com/Articles/7568/Tail-NET
    Dann brauchst du nicht jedesmal durch die komplette Datei gehen.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --