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.