Forum: PC-Programmierung logrotate selber bauen?


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Bauform B. (bauformb)


Lesenswert?

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...

von Εrnst B. (ernst)


Lesenswert?

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)

von Peter C. (peter_c49)


Lesenswert?

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

von Ein T. (ein_typ)


Lesenswert?

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

von Monk (roehrmond)


Lesenswert?

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.

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

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
von Bauform B. (bauformb)


Lesenswert?

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.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

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
Noch kein Account? Hier anmelden.