Tail -f auf neue Dateien

7

Gibt es eine Möglichkeit, so etwas zu tun:

tail -f logs/

und das stdout in jeder Zeile aktualisieren, die zu jeder Datei hinzugefügt wird, die bereits in den Protokollen vorhanden ist, und zu jeder Datei, die in den Protokollen erstellt wird, nachdem der Befehl ausgegeben wurde?

Jack
quelle

Antworten:

7

Vielen Dank für die Unterstützung, aber da weder Mutitail noch Tail -F noch Watch Tail für das, was ich brauche, zu helfen scheinen, habe ich eine kleine Lösung in C entwickelt. Ich poste den Code hier, da ihn vielleicht jemand nützlich finden kann. (Es gibt fehlende Schecks und einige Schwächen, die ich kenne, aber bisher ist es genug)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <signal.h>
#include <dirent.h>
#include <linux/limits.h>
#define    CHAR_BACK   500

// * File handler structure
struct file_followed { long last_position; char filename[NAME_MAX]; struct file_followed * next; };
struct file_followed * file_list = NULL;

// * To quit peacefully
int cycle = 1;
void stopCycle(int u) { cycle = 0; }

// * Last tailed filename
char last_tailed[NAME_MAX];

void fileAdd(char * file) {
    struct file_followed ** list = &file_list;
    struct stat statdesc;

    if(stat(file, &statdesc) || !S_ISREG(statdesc.st_mode)) { return; }
    while(*list) { list = &((*list)->next); }
    *list = malloc(sizeof(struct file_followed));
    (*list)->last_position = -1;
    strcpy((*list)->filename, file);
    (*list)->next = NULL;
}

int fileTail(struct file_followed * item) {
    int ret = 0;
    FILE * fp = fopen(item->filename, "r");
    fseek(fp, 0, SEEK_END);
    long end_position = ftell(fp);

    if( end_position != item->last_position ) {
        if(strcmp(item->filename, last_tailed)) { strcpy(last_tailed, item->filename); printf("\n** %s **:\n", item->filename); }

        int start_position = item->last_position == -1 || item->last_position > end_position ? (end_position-CHAR_BACK > 0 ? end_position-CHAR_BACK : 0) : item->last_position;
        fseek(fp, start_position, SEEK_SET);

        int len = end_position - start_position;
        char * buf = malloc(len+1);
        fread(buf, len, 1, fp);
        buf[len] = '\0';
        printf("%s%s", len == CHAR_BACK ? "[...]" : "", buf);
        free(buf);

        item->last_position = end_position;
        ret = 1;
    }

    fclose(fp);
    return ret;
}

void fileRem(char * file) {
    struct file_followed ** list = &file_list;
    while(*list && strcmp((*list)->filename, file)) { list = &((*list)->next); }
    if(*list) { struct file_followed * todel = *list; *list = (*list)->next; free(todel); }
}

int main(int argc, char ** argv) {

    struct dirent **namelist;
    struct stat statdesc;
    struct timeval tv;
    fd_set set;
    int fd;
    int wd;
    int r;

    // * Help
    if(stat(argv[1], &statdesc) || !S_ISDIR(statdesc.st_mode)) { printf("[usage] %s dir-to-monitor\n", argv[0]); exit(EXIT_FAILURE); }

    // * Init
    chdir(argv[1]);
    memset(last_tailed, 0, sizeof(last_tailed));
    signal(SIGINT, stopCycle);
    signal(SIGTERM, stopCycle);

    // * Inotify
    if( (fd = inotify_init()) < 0) { perror("inotify_init"); }
    if( (wd = inotify_add_watch( fd, ".", IN_CREATE | IN_DELETE ) < 0)) { perror("inotify_add_watch"); }

    // * File add recursively on dirscan
    if( (r = scandir(".", &namelist, 0, alphasort)) < 0) { perror("scandir"); }
    while (r--) { fileAdd(namelist[r]->d_name); free(namelist[r]); }
    free(namelist);

    // * Neverending cycle
    while(cycle) {
        // * Select on inotify
        FD_ZERO(&set);
        FD_SET(fd, &set);
        tv.tv_sec = 0;
        tv.tv_usec = 1000;
        if( (r = select(fd+1, &set, NULL, NULL, &tv)) == -1) { perror("select"); }

        // * New add or del on inotify
        if(r) {
            struct inotify_event * event;
            char buf[1024];
            if(read(fd, buf, 1024) <= 0) { perror("read"); }
            event = (struct inotify_event *) buf;
            if(event->mask & IN_CREATE) { fileAdd(event->name); } 
            else if(event->mask & IN_DELETE) { fileRem(event->name); }
        }

        // * Check for new tails
        struct file_followed * list = file_list;
        int tailers = 0;
        while(list) { tailers += fileTail(list); list = list->next; }
        if(!tailers) { usleep(500000); }
    }

    // * Stop inotify
    inotify_rm_watch( fd, wd );
    close(fd);

    return EXIT_SUCCESS;
}
Jack
quelle
Sie, Sir, sind ein Genie. Ich habe schon eine Weile nach so etwas gesucht. Ich habe die Ausgabe einer App, deren Datum protokolliert ist und deren aktueller Schwanz keine neuen Tage aufnimmt. Das funktioniert perfekt. Verschieben Sie dies auf githuboder ähnlich und lassen Sie es verbessern (nur basierend auf den von Ihnen angegebenen Überprüfungen). Haben Sie dies auf den tailursprünglichen Code gestützt oder wurde er von Grund auf neu erstellt?
Madivad
1
Ich bin froh, dass es jemandem geholfen hat. Ich habe das von Grund auf neu geschrieben, Logik ist dank inotify api recht einfach. So einfach, dass ich nie daran gedacht hätte, es zu githubben. Fühlen Sie sich frei, es zu tun, wenn Sie denken, dass es sich lohnt
Jack
2
G'day Sir Jack, ich habe mir die Freiheit genommen, dies zu Github hinzuzufügen, da es eines der besten kleinen Dienstprogramme ist, auf die ich seit Ewigkeiten gestoßen bin, und ich habe es jetzt buchstäblich rund um die Uhr. Bitte zögern Sie nicht, das Repo hinzuzufügen / zu ändern / die Kontrolle zu übernehmen, und wenn Sie dies wünschen, werde ich es Ihnen gerne übergeben (wenn Sie mir sagen, wie), da ich nicht weiß, ob ich die erforderlichen Fähigkeiten habe, um es gerecht zu machen . Nochmals vielen Dank für die Beantwortung einer plagenden Frage von mir und möglicherweise anderen :) Prost!
Madivad
Du bist großartig, Madivad. Ich habe die Seite gesehen und sie geliebt. Du hast es sehr gut gemacht, danke!
Jack
3

Ich habe Änderungen an https://serverfault.com/a/542580/203373 vorgenommen , um einige Kompilierungsfehler auf meinem System (unter Ubuntu Linux) zu beheben. Ich habe Casts zu (struct file_followed*)und hinzugefügt (char*)und IN_MODIFYin die Liste zum Hinzufügen von Überwachungsdateien aufgenommen, um nach Änderungen an aktuellen Dateien zu suchen. Diese Zeile wurde hinzugefügt:

if(event->mask & IN_MODIFY) { fileMod(event->name, file_list); }

und die fileModFunktion

void fileMod(char* fileName, struct file_followed* file_list)

um zu überprüfen, ob eine geänderte Datei abgeschnitten wurde, und auszudrucken, ob sie geändert wurde, und um sie zu aktualisieren item->last_position = -1, damit die Datei erneut gedruckt wird .

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <signal.h>
#include <dirent.h>
#include <linux/limits.h>
#define    CHAR_BACK   500

// * File handler structure
struct file_followed { long last_position; char filename[NAME_MAX]; struct file_followed * next; };
struct file_followed * file_list = NULL;

// * To quit peacefully
int cycle = 1;
void stopCycle(int u) { cycle = 0; }

// * Last tailed filename
char last_tailed[NAME_MAX];

void fileAdd(char * file) {
    struct file_followed ** list = &file_list;
    struct stat statdesc;

    if(stat(file, &statdesc) || !S_ISREG(statdesc.st_mode)) { return; }
    while(*list) { list = &((*list)->next); }
    *list = (struct file_followed*)malloc(sizeof(struct file_followed));
    (*list)->last_position = -1;
    strcpy((*list)->filename, file);
    (*list)->next = NULL;
}

void fileMod(char* fileName, struct file_followed* file_list) {
    struct file_followed* item = file_list;
    while(item) { 
        if(strcmp(item->filename, fileName) == 0) {
            FILE* fp = fopen(item->filename, "r");
            fseek(fp, 0, SEEK_END);
            long end_position = ftell(fp);
            fclose(fp);
            if (end_position <= item->last_position) {
                printf("\n** %s truncated **\n", fileName);
                item->last_position = -1;
            }
            usleep(100);
            return;
        }
        item = item->next;
    }
}

int fileTail(struct file_followed * item) {
    int ret = 0;
    FILE * fp = fopen(item->filename, "r");
    fseek(fp, 0, SEEK_END);
    long end_position = ftell(fp);

    if( end_position != item->last_position ) {
        if(strcmp(item->filename, last_tailed)) { strcpy(last_tailed, item->filename); printf("\n** %s **:\n", item->filename); }

        int start_position = item->last_position == -1 || item->last_position > end_position ? (end_position-CHAR_BACK > 0 ? end_position-CHAR_BACK : 0) : item->last_position;
                    fseek(fp, start_position, SEEK_SET);

        int len = end_position - start_position;
        char * buf = (char*)malloc(len+1);
        fread(buf, len, 1, fp);
        buf[len] = '\0';
        printf("%s%s", len == CHAR_BACK ? "[...]" : "", buf);
        free(buf);

        item->last_position = end_position;
        ret = 1;
    }

    fclose(fp);
    return ret;
}

void fileRem(char * file) {
    struct file_followed ** list = &file_list;
    while(*list && strcmp((*list)->filename, file)) { list = &((*list)->next); }
    if(*list) { struct file_followed * todel = *list; *list = (*list)->next; free(todel); }
}

int main(int argc, char ** argv) {

    struct dirent **namelist;
    struct stat statdesc;
    struct timeval tv;
    fd_set set;
    int fd;
    int wd;
    int r;

    // * Help
    if(stat(argv[1], &statdesc) || !S_ISDIR(statdesc.st_mode)) { printf("[usage] %s dir-to-monitor\n", argv[0]); exit(EXIT_FAILURE); }

    // * Init
    chdir(argv[1]);
    memset(last_tailed, 0, sizeof(last_tailed));
    signal(SIGINT, stopCycle);
    signal(SIGTERM, stopCycle);

    // * Inotify
    if( (fd = inotify_init()) < 0) { perror("inotify_init"); }
    if( (wd = inotify_add_watch( fd, ".", IN_CREATE | IN_MODIFY |IN_DELETE ) < 0)) { perror("inotify_add_watch"); }

    // * File add recursively on dirscan
    if( (r = scandir(".", &namelist, 0, alphasort)) < 0) { perror("scandir"); }
    while (r--) { fileAdd(namelist[r]->d_name); free(namelist[r]); }
    free(namelist);

    // * Neverending cycle
    while(cycle) {
        // * Select on inotify
        FD_ZERO(&set);
        FD_SET(fd, &set);
        tv.tv_sec = 0;
        tv.tv_usec = 1000;
        if( (r = select(fd+1, &set, NULL, NULL, &tv)) == -1) { perror("select"); }

        // * New add or del on inotify
        if(r) {
            struct inotify_event * event;
            char buf[1024];
            if(read(fd, buf, 1024) <= 0) { perror("read"); }
            event = (struct inotify_event *) buf;
            if(event->mask & IN_MODIFY) { fileMod(event->name, file_list);} 
            else if(event->mask & IN_CREATE) { fileAdd(event->name); } 
            else if(event->mask & IN_DELETE) { fileRem(event->name); }
        }

        // * Check for new tails
        struct file_followed * list = file_list;
        int tailers = 0;
        while(list) { tailers += fileTail(list); list = list->next; }
        if(!tailers) { usleep(500000); }
    }

    // * Stop inotify
    inotify_rm_watch( fd, wd );
    close(fd);

    return EXIT_SUCCESS;
}
Ekangas
quelle
Ich habe viele Programmierprobleme auf uva.onlinejudge.org gemacht und nach einem Tool gesucht, mit dem ich alle meine Ausgabedateien in einem Ausgabeordner, einschließlich neuer, verwalten und aktualisieren kann, wenn sie abgeschnitten werden. Dies scheint die beste Lösung dafür zu sein.
Ekangas
1

Ich denke nicht, dass es einen Weg gibt, nur zu verwenden tail, aber Sie sollten in der Lage sein, den gleichen Effekt watchzusammen mit zu erzielen tail. Die einzige Gefahr besteht dann darin, sicherzustellen, dass kein Verzeichnis erstellt wird, sondern nur eine neue Datei. Dies kann gemindert werden, indem sichergestellt wird, dass Sie einen geeigneten Shell-Glob verwenden, der an tail übergeben wird. Beispiel:watch -n 2 tail *.log

John
quelle
1
Ja, ich glaube nicht, dass es einen Weg mit dem Schwanz gibt. Aber die Uhr ist weit entfernt von dem, wonach ich suche
Jack
Ich habe watchfür mehrere ähnliche Aufgaben verwendet und leider fällt es zu kurz. Ich denke, der größte Mangel ist mein Wissen, weil ich immer Fehler bekomme, wenn die Befehlszeile für mich gut aussieht.
Madivad
0

Du könntest benutzen: tail -F logs/*

Bonus-Tipp: Schauen Sie sich Multitail an , es ist ein großartiger kleiner Befehl.

Beispiel : Führen Sie ALLE Apache-Protokolldateien (* access_log / * error_log) in einem Fenster zusammen:

multitail -cS apache --mergeall /var/log/apache2/*access_log --no-mergeall \  
  -cS apache_error --mergeall /var/log/apache2/*error_log --no-mergeall

Zeigen Sie 5 Protokolldateien an, während Sie 2 zusammenführen, und fügen Sie sie in 2 Spalten ein, wobei sich nur eine in der linken Spalte befindet:

multitail -s 2 -sn 1,3  /var/log/apache/access.log -I /var/log/apache/error.log \ 
  /var/log/messages /var/log/mail.log /var/log/syslog
rafi
quelle
Vielen Dank für Multitail, es ist ein großartiges Tool. Jedenfalls hat noch niemand bei der ursprünglichen Frage geholfen. Wenn Sie die Shell "tail -F logs / *" ausführen, wird der Stern mit allen Dateien erweitert, die derzeit in logs / vorhanden sind. Daher wird die Datei, die nach der Ausgabe des Befehls erstellt wird, nicht von tail gelesen
Jack
Ich muss hinzufügen, ich LIEBE multitail, und ist eines der ersten Tools, die ich auf einem neuen System installiere. Besonders für alle /var/logDateien, die Sie überwachen möchten. Wie bei jedem derzeit vorhandenen Tool gibt es jedoch keine Möglichkeit, NEUE Dateien zu überwachen, die nach dem Aufruf erstellt werden. Mit Ausnahme von @ Jacks Beitrag oben, als er die Antwort akzeptierte
Madivad
0

Möglicherweise können Sie so etwas verwenden, multitailum mehrere Dateien gleichzeitig zu speichern. Wenn Sie dies mit der -FOption kombinieren (wiederholen Sie den Vorgang, falls die Datei nicht vorhanden ist), erhalten Sie möglicherweise das, wonach Sie suchen.

Bob
quelle
-F für Multitail ist für Konfigurationsdatei
Jack