hallo, ein Linux-C-Programm schreibt alles mögliche mit fprintf() auf stdout und stderr und unabhängig davon mit write() auf fd 1 und fd 2. Alles landet in einem gemeinsamen Logfile. Das funktioniert praktisch überraschend gut. Aber jetzt soll das Programm nach x MByte ein neues File aufmachen. Gegen die Mischung von fprintf() und write() sollte fflush(stdout); fflush(stderr) helfen. Aber wie geht's dann weiter? Geht das ohne close()? Oder wie würde open() nach einem close() funktionieren? Noch dazu für beide in eine Datei? Bonus-Problem: per Konvention ist der Name des Logfiles gleich dem Namen des Programms, aber das muss ja nicht stimmen...
Wenn dein Programm auf STDOUT/ERR loggt, kriegst du das Rotieren am einfachsten von Extern hin. Entweder wird das program per systemd o.Ä. gestartet, dann kann sich das um das Rotieren kümmern, oder z.B. mein_programm | split -c 1M -d - /var/log/mein_programm_log Dann kriegst du fortlaufend nummerierte Logfiles mit je 1MB, leider nicht am Zeilenende abgegrenzt... (split ist in coreutils, gibt auch ähnliche Tools die das nicht nach Zeilen/Byte-Anzahl sondern nach Datum machen)
Hallo bauformb, für so etwas baut man sich einen signalhandler ein. zb SIGUSR1. wenn dieses signal kommt, dann kann man ein internes logrotate (close log,open log) machen. mfG Peter
Bauform B. schrieb: > ein Linux-C-Programm schreibt alles mögliche mit fprintf() auf stdout > und stderr und unabhängig davon mit write() auf fd 1 und fd 2. Alles > landet in einem gemeinsamen Logfile. Das funktioniert praktisch > überraschend gut. Aber jetzt soll das Programm nach x MByte ein neues > File aufmachen. > > Gegen die Mischung von fprintf() und write() sollte fflush(stdout); > fflush(stderr) helfen. Aber wie geht's dann weiter? Geht das ohne > close()? Oder wie würde open() nach einem close() funktionieren? Noch > dazu für beide in eine Datei? > > Bonus-Problem: per Konvention ist der Name des Logfiles gleich dem Namen > des Programms, aber das muss ja nicht stimmen... Warum sollte man das selbst bauen? Ich meine, systemd-journald(8), logger(1) und logrotate(8) existieren, und obendrein ungefähr drölfzig Millionen Libs, die das können -- diese hier [1] zum Beispiel soll das können. In Java gibts log4j, in Pythons logging-Modul den RotatingFileHandler, und für Golang kann lumberjack benutzt werden... wobei so eine Logmessage so schnell wie möglich abgefackelt sein sollte, damit es mit seiner eigentlichen Arbeit weitermachen kann. Rotation und Komprimierung sind in einem externen Programm (also einem wie logrorate(8)) oder einem eigenen Thread meist besser aufgehoben. Dieses externe Programm stößt das loggende Programm dann per Signal an, wie das von Peter bereits vorgeschlagen worden ist. Dazu möchtest Du eventuell mal einen Blick auf freopen(3) werfen. [1] https://github.com/yksz/c-logger
Bauform B. schrieb: > Geht das ohne close()? Windows: Man kann keine Dateien löschen oder umbenennen, während sie geöffnet sind. Linux: Wenn eine offene Datei umbenannt wird, kann sie weiterhin beschrieben werden. Es wurde nur der Name geändert, nicht der Speicherplatz. Wenn eine offene Datei gelöscht wird, existiert sie für das aktive Programm und auf dem Speichermedium weiterhin und kann immer noch beschrieben werden. Sie wird erst später beim Schließen gelöscht. Andere Programme sehen die Datei aber sofort nicht mehr. Nur unter Linux kannst du close() umgehen, indem du ein Backup vom aktuellen Logfile anlegst (Inhalt kopieren) und dann das aktuelle File mit truncate() zurück setzt. Im meine (bin nicht 100% sicher), dass die Datei dazu mit der Option o_append geöffnet worden sein muss, sonst klappt das implizite Zurücksetzen des Positionszeigers nicht. Unter Linux sollte Logrotate die erste Wahl sein. Es kostet etwas mehr Aufwand zur Konfiguration, dafür ist es aber auch flexibler und gut erprobt.
write / fwrite Funktionen sollte man eigentlich nicht mischen. Zum loggen ist es meistens am besten, wenn man eine eigene log Funktion nutzt. Die kann man dann was auf stderr oder fd 2 ausgeben lassen, oder auf man kann sie vsyslog aufrufen lassen. Auf stdout und stderr würde ich auch nicht loggen, wenn schon alles auf stderr. Oder zumindest irgendwie konsistent. Sofern stderr/stdout nicht per freopen neu geöffnet wurden, werden die aif STDOUT_FILENO/STDERR_FILENO (normalerweise 1/2) zeigen, Dann kann man einfach die FDs austauschen, z.B. so: (ungetestet)
1 | #define _DEFAULT_SOURCE
|
2 | #include <fcntl.h> |
3 | #include <unistd.h> |
4 | #include <stdio.h> |
5 | |
6 | int redirect_to_logfile(const char* path){ |
7 | if(fileno(stdout) != STDOUT_FILENO || fileno(stderr) != STDERR_FILENO){ |
8 | // This is bad, it has been changed using fdreopen!
|
9 | // We can get a file object from an fd using fdopen, we can reopen a file using freopen, but there is no fdreopen function!
|
10 | dprintf(STDERR_FILENO, "Error: one of stdout / stderr does not correspond to STDOUT_FILENO / STDERR_FILENO anymore (did someone use fdreopen?)\n"); |
11 | return -1; |
12 | }
|
13 | int res = open(path, O_WRONLY|O_APPEND|O_CREAT|O_CLOEXEC, 0644); |
14 | if(res == -1){ perror("failed to open new logfle"); return -1; } |
15 | fflush(stdout); |
16 | fflush(stderr); |
17 | // Note, cloexec is per file descriptor, not file description, it will not be set for STDOUT_FILENO
|
18 | if(dup2(res, STDOUT_FILENO) == -1){ perror("failed to override stdout fd"); return -1; } |
19 | if(dup2(res, STDERR_FILENO) == -1){ perror("failed to override stderr fd"); return -1; } |
20 | if(res != STDOUT_FILENO && res != STDERR_FILENO) |
21 | close(res); |
22 | return 0; |
23 | }
|
24 | |
25 | int main(){ |
26 | puts("A"); |
27 | redirect_to_logfile("test.log"); |
28 | puts("B"); |
29 | }
|
Sollte man aber auch nicht machen. Problem hierbei ist, wenn man von dem Programm ein anderes Programm startet, erbt es alle FDs, die nicht CLOEXEC sind. Wenn Programm 1 dann die FDs ersetzt, bekommt Programm 2 davon nichts mit / schreibt immer noch ins alte File. Beim loggen mit der syslog() Funktion wird glaube ich für jede Zeile die Datei neu geöffnet. (eigentlich wird nach /dev/syslog geschrieben, ein socket des syslog daemon, und der macht das dann). Logrotate ist normalerweise ein separates Programm, welches die Dateien einfach verschiebt, eventuell zippt, jenachdem, was man einstellt, weil man darf nicht mehr in die danach nicht mehr existierende Datei schreiben. Bei Systemd stellt systemd-journald /dev/syslog bereit, und logt in sein Journal. Es gibt noch die sd_journal_* Funktionen, die sollte man vermeiden, denn die gehen nur auf Systemd Systemen. Statt dein Programm zu ändern, kannst du auch einfach stdout und stderr mit einer Pipe in ein anderes Programm umleiten, z.B. so:
1 | program 2>&1 | logger -t program |
logger ist ein Program zum Loggen nach syslog, kann log meldungen von stdout nehmen. Du kannst natürlich auch in deinem Program logger per fork & exec starten, und mit pipe und dup2 stdout & stderr da hin umleiten, statt es extern per Shell zu machen. Ein makeshift logrotate ist das aber nicht. Kann man sich aber auch ein Programm dafür machen, welches ähnlich funktioniert. Von stdin lesen, in ein file schreiben, irgendwann ein neues öffnen. Man könnte auch machen, dass ein Programm sich selbst nochmal startet, mit einem Argument dass es logs entgegen nimmt, per /proc/self/exe. Oder einfacher ein Thread und eine pipe.
:
Bearbeitet durch User
Daniel A. schrieb: > Problem hierbei ist, wenn man von dem > Programm ein anderes Programm startet, erbt es alle FDs, die nicht > CLOEXEC sind. Wenn Programm 1 dann die FDs ersetzt, bekommt Programm 2 > davon nichts mit / schreibt immer noch ins alte File. Und wenn sie CLOEXEC sind, schreibt Programm 2 irgendwo hin. Beides könnte man wohl umgehen, aber das wird viel zu unübersichtlich. Damit hat sich der Plan erledigt, vielen Dank (lieber ein Ende mit Schrecken und so) > Man könnte auch machen, dass ein Programm sich selbst nochmal startet oder sich einfach beendet und von "einer höheren Instanz" neu gestartet wird. Das gibt's ja fast geschenkt, inkl. neuem Logfile. Nur schade, dass die anderen netten Ideen so nicht genutzt werden. Deshalb ein doppeltes Dankeschön an alle.
Hab mir das damals mal selber gebaut, hauptsächlich aus spaß. Python, C++ und Rust, aber es ist nur noch die Rust version übrig. https://gitlab.com/Bloody_Wulf/easylog/-/tree/master/src?ref_type=heads
:
Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.