Open Source im professionellen Einsatz
Linux-Magazin 06/2001

Logfiles effektiv überwachen

Täglich auf Zack

Wer hat schon Zeit, täglich seine Logdateien zu kontrollieren? Das im Folgenden vorgestellte Skript fasst alle aktuellen Veränderungen in einem Report zusammen und verschickt sie einmal täglich per E-Mail.

1289

Logdateien halten wichtige Ereignisse fest und helfen dabei, auftretende Fehler einzukreisen. Beim Überwachen aktiver Prozesse nützen die gesammelten Daten aber nur, wenn sich jemand aktiv um sie kümmert und täglich kontrolliert, ob noch alles ordnungsgemäß läuft. Logdateien wachsen sehr schnell, so dass wir einen Mechanismus brauchen, der nur die seit dem letzten Prüftermin hinzugekommenen Daten analysiert.

Auf Perlmeister.com gibt es beispielsweise die Datei rbsub.txt, die festhält, welche Leute sich auf die Liste für Amerika-Rundbriefe setzen ließen: Wenn jemand das entsprechende Web-Formular ausgefüllt hat, wächst rbsub.txt um eine Zeile, die die eingetragene E-Mail-Adresse enthält. Zusätzlich protokolliert die Datei errors .txt, ob irgendwo auf dem Webserver Fehler aufgetreten sind.

Tägliche Kontrolle per Skript

Das Skript logmail.pl läuft einmal täglich per Cronjob, merkt sich die aktuelle Größe der Logdateien, stellt hinzugekommene Zeilen fest, fasst sie in einem Report zusammen und verschickt ihn per E-Mail an mich. So sehe ich beim täglichen E-Mail-Lesen, ob noch alles in Ordnung ist.

Listing logmail.pl zeigt die Implementierung. Die Konfigurationssektion am Anfang legt eine Reihe von Parametern fest: In $DATABASE steht, wie die Datei heißt, in der das Skript sich merkt, welche Dateien es schon gelesen hat.

Die E-Mail mit den Logdaten ist da!

Die Werte für $FROM, @TO und $SUBJECT geben für die später zu verschickende E-Mail an, woher sie stammt, wohin sie geht und was in der Betreffzeile steht. $MAXLINES legt fest, wie viele Zeilen pro Logdatei maximal in den E-Mail-Report kommen. Der letzte Parameter $B gibt ein Verzeichnis an, in dem die Dateien stehen, damit wir es später nicht hundertmal tippen müssen.

In Zeile 10 steht ein Array, das Logdateipfaden aussagekräftige Namen zuordnet. Die geraden Elemente des Arrays enthalten die Pfadnamen, die ungeraden die Aliasnamen. Zeile 17 deklariert den Hash %offs, der jeweils unter dem Pfadnamen der Logdatei abspeichert, wie viele Bytes logmail .pl beim letzten Aufruf schon aus der Datei ausgelesen hat, also im aktuellen Durchlauf überspringen kann.

Die Zeilen 14 und 15 holen die Module Mail::Mailer und Storable, das erste zum Verschicken von E-Mail, das zweite zum Speichern von Daten, die die Laufzeit von logmail.pl in einer Datei überleben und beim nächsten Aufruf wieder bereitstehen. Beide kommen frisch vom CPAN und werden wie üblich installiert:

perl -MCPAN -eshell
cpan> install Storable
cpan> install Mail::Mailer

Speichern und Laden mit Storable

Während man Hashes sonst mit tie und DBM-Dateien persistent macht, wollen wir jetzt mal Storable verwenden, das sich durch ein bestechend klares Interface auszeichnet: store() friert Daten in einer angegebenen Datei ein und restore() holt sie wieder zurück - einfacher geht's nicht.

Beide Funktionen arbeiten mit einer Referenz auf die beliebig komplexe Datenstruktur. Folgender Code legt einen einfachen Hash in der Datei datei.dat ab:

use Storable;

$offs = { "rbsub.txt"  => 257,
          "errors.txt" => 12 };

    # Abspeichern
store($offs, "datei.dat");

$offs ist eine Referenz auf einen anonymen Hash, der im {}-Konstrukt entstand. retrieve() holt die Datenstruktur wieder aus der Versenkung:

use Storable;

    # Zurückholen
$offs = retrieve("datei.dat");

    # => 257
print "$offs->{rbsub.txt}n";
    # => 12
print "$offs->{errors.txt}n";

Sowohl store() als auch retrieve() liefern undef zurück, falls etwas schief geht, weshalb die Zeilen 21 und 76 in logmail.pl ordnungsgemäß den Rückgabewert prüfen und notfalls das Programm abbrechen.

Die while()-Schleife ab Zeile 27 holt paarweise Werte aus dem @FILES- Array. Der erste Wert ist, wie in Zeile 10 festgelegt, immer der beschreibende Text, der zweite Wert ist der Pfad zur jeweiligen Logdatei. In der Variablen $mailtext steht nach Abschluss der Loganalyse der fertige Report.

Die Zeilen 30 bis 32 schreiben eine Überschrift für die aktuell analysierte Datei, eingerahmt von zwei Zeilen, in denen jeweils 70-mal das Zeichen # steht. Zeile 35 findet heraus, wie viele Zeichen der Datei bereits während früherer Aufrufe von logmail.pl analysiert wurden. Die werden übersprungen, denn schließlich soll logmail.pl nicht jedes Mal bei Adam und Eva anfangen. Existiert im persistenten Hash bereits ein Eintrag zur gerade analysierten Logdatei, dann steht $offset nach Zeile 35 auf dem gespeicherten Wert. Andernfalls setzt es die ||-Verknüpfung auf 0.

Zeile 37 öffnet schließlich die Logdatei zum Lesen. Die nachfolgende if-Bedingung prüft, ob der gespeicherte Offset überhaupt sinnvoll ist. Ist er größer als die Datei selbst (was sich leicht mit -s herausfinden lässt), wurde die Datei offensichtlich manuell verkürzt und logmail.pl schreibt statt eines Reports eine Warnmeldung nach $mailtext.

Entspricht der Offset den Erwartungen, springt Zeile 45 die angegebene Anzahl von Bytes mittels der seek()-Funktion nach vorn. Zeile 48 liest die dann folgenden Zeilen bis zum Zeilenende in den Array @data. $noflines ist die Anzahl der Elemente in @data, also die der neuen Zeilen seit dem letzten Aufruf von logmail.pl. Zeile 52 stellt den persistenten Offset auf jenes Dateiende ein, auf das das Filehandle FILE gerade zeigt und dessen Offset vom Dateianfang daher die tell()-Funktion zurückgibt.

Listing: logmail.pl

01 #!/usr/bin/perl -w
02 
03 my $FROM     = `absender@irgendwo.com';
04 my @TO       = qw(logs@perlmeister.com);
05 my $SUBJECT  = `Today's Logs';
06 my $MAXLINES = 10;
07 my $DATABASE = `/home/mschilli/tmp/logmail.dat';
08 my $B        = `/home/mschilli/tmp';
09 
10 my @FILES = ("Neukunden"  => "$B/rbsub.txt",
11              "Fehler"     => "$B/errors.txt",
12             );
13 use strict;
14 use Mail::Mailer;
15 use Storable;
16 
17 my $offs = {};
18 
19 if(-r $DATABASE) {
20         # Gespeicherte Daten auslesen
21     my $offs = retrieve($DATABASE) or 
22         die "Cannot open $DATABASE";   
23 }
24 
25 my $mailtext = "";
26 
27 while(my ($text, $file) = splice(@FILES, 0, 2)) {
28 
29         # Teilüberschrift
30     $mailtext .= "n" . "#" x 70;
31     $mailtext .= "n$text:n";
32     $mailtext .= "#" x 70 . "n";
33 
34         # Gespeicherter Offset vom letzten Mal
35     my $offset = ($offs->{$file} || 0);
36 
37     if(open(FILE, "<$file")) {
38         if(-s $file < $offset) {
39                 # Datei wurde gekürzt
40             $offs->{$file} = -s $file;
41             $mailtext .= "(Offset adjusted)n";
42         } else {
43                 # Bis zum gespeicherten Offset
44                 # vorfahren
45             seek(FILE, $offset, 0);
46 
47                 # Rest zeilenweise auslesen
48             my @data = <FILE>;
49             my $noflines = @data;
50 
51                 # Neuen Offset einstellen
52             $offs->{$file} = tell(FILE);
53 
54                 # Kürzen falls > $MAXLINES
55             if(@data > $MAXLINES) {
56                 splice(@data, $MAXLINES/2, 
57                        @data-$MAXLINES, "...n");
58             }
59             my $data = join `', @data;
60 
61                 # Zeilenzahl ausgeben
62             $mailtext .= sprintf "(%d line%s)n", 
63                        $noflines,
64                        $noflines == 1 ? "" : "s";
65                 # Logdaten ausgeben
66             $mailtext .= $data;
67             close(FILE);
68         }
69     } else {
70         $mailtext .= "Cannot open $filen";
71     }
72 }
73 
74 mailto($SUBJECT, $mailtext, $FROM, @TO);
75 
76 store($offs, $DATABASE) or 
77     die "Cannot store data in $DATABASE";
78 
79 ##################################################
80 sub mailto {
81 ##################################################
82     my ($subj, $text, $from, @to) = @_;
83 
84     return if @to == 0;
85 
86     my $mailer = Mail::Mailer->new( `sendmail' );
87     my $to = join ", ", @to;
88 
89     $mailer->open( { "To"       => $to,
90                      "From"     => $from,
91                      "Subject"  => $subj,
92                      "Reply-To" => $from,
93                    } ) or die "open failed";
94 
95     print $mailer $text;
96     $mailer->close();
97 }

Die fertige E-Mail soll nicht mit Logzeilen vollgestopft sein, also wird die Anzahl der auszugebenden Zeilen auf $MAXLINES begrenzt. Dazu schneidet die splice()-Funktion in Zeile 56 den Mittelteil aus @data heraus, so dass am linken und am rechten Ende genau je $MAXLINES/2 Elemente übrig bleiben. Den mittleren Teil ersetzt eine so genannte Ellipse.

Zeile 59 fasst die einzelnen Zeilen zu einem langen Textstring zusammen. Zeile 62 sorgt dafür, dass logmail.pl die Anzahl der neuen Zeilen in der Logdatei korrekt (Einzahl oder Mehrzahl) anzeigt: 0 lines, 1 line, 2 lines. Zeile 66 hängt die gesammelten Logdaten an den Mailtext, Zeile 67 schließt die Logdatei.

Sind alle Dateien aus @FILES abgearbeitet, schickt Zeile 74 den Report mit der mailto()-Funktion (siehe folgenden Abschnitt) an die in der Parametersektion stehende Adresse. Die store()-Funktion in Zeile 76 schreibt die unter der Referenz $offs stehende Datenstruktur mit den Offsets der Logdateien zurück auf die Festplatte.

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Füttern nach Programm

    Englische Vokabeln oder Vim-Kommandos in mundgerechten Stücken serviert sind ungleich verdaulicher als jedes Supersize-Mahl. Das diesmal im Snapshot vorgestellte Skript macht Lernwillige zu Verkostern eines täglich wechselnden E-Mail-Häppchens.

  • Licht ins Dunkel

    Im Dunkeln ist gut munkeln - doch damit ist jetzt Schluss: Der hier vorgestellte Perl-Daemon holt verborgene Vorgänge im Netz ans Licht und schlägt Alarm, wenn ihm etwas verdächtig vorkommt.

  • Sympathischer Vorleser

    Dank des Asterisk Gateway Interface entwickeln Sie eigene Telefonie-Anwendungen mit ihrer Lieblings-Programmiersprache. Der Workshop erklärt die Grundlagen von AGI anhand einer putzigen Anwendung, die Anrufern zu per Telefontastatur eingegebenen Begriffen die zugehörigen Wikipedia-Artikel vorliest.

  • Episodenfilm

    Ein Perl-Skript mit GTK-2-Oberfläche merkt sich, wie weit sein Anwender gespeicherte Videos angesehen hat, und fährt auf Wunsch an der Stelle der letzten Unterbrechung wieder fort.

  • Logfiles

    In den Logdateien eines Linux-Systems und seiner Dienste rauscht vorüber, was auf dem Rechner passiert. Tools wie Logcheck und Logsurfer filtern die wichtigsten Ereignisse für den Admin heraus – oder leiten sogar automatisch die passende Reaktion ein.

comments powered by Disqus

Ausgabe 07/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.