Workshop o mikrokontrolérech na SKSP 2024.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
4.6 KiB

2 months ago
/*
* UCW Library -- Logging to Files
*
* (c) 1997--2015 Martin Mares <mj@ucw.cz>
* (c) 2008 Tomas Gavenciak <gavento@ucw.cz>
*
* This software may be freely distributed and used according to the terms
* of the GNU Lesser General Public License.
*/
#include <ucw/lib.h>
#include <ucw/log.h>
#include <ucw/log-internal.h>
#include <ucw/io.h>
#include <ucw/threads.h>
#include <ucw/simple-lists.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
struct file_stream {
struct log_stream ls; // ls.name is the current name of the log file
int fd;
uint flags; // FF_xxx
char *orig_name; // Original name with strftime escapes
};
#define MAX_EXPAND 64 // Maximum size of expansion of strftime escapes
static int log_switch_nest;
static bool log_stderr_replaced;
static void
do_log_reopen(struct file_stream *fs, const char *name)
{
int fd = ucw_open(name, O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd < 0)
die("Unable to open log file %s: %m", name);
if (fs->fd >= 0)
close(fs->fd);
fs->fd = fd;
if (fs->flags & FF_FD2_FOLLOWS)
{
dup2(fd, 2);
log_stderr_replaced = 1;
}
if (fs->ls.name)
{
xfree(fs->ls.name);
fs->ls.name = NULL; // We have to keep the stream consistent -- die() below can invoke logging
}
fs->ls.name = xstrdup(name);
}
static int
do_log_switch(struct file_stream *fs, struct tm *tm)
{
if (!(fs->flags & FF_FORMAT_NAME))
{
if (fs->fd >= 0)
return 1;
else
{
do_log_reopen(fs, fs->orig_name);
return 1;
}
}
int buflen = strlen(fs->orig_name) + MAX_EXPAND;
char name[buflen];
int switched = 0;
ucwlib_lock();
if (!log_switch_nest) // Avoid infinite loops if we die when switching logs
{
log_switch_nest++;
int l = strftime(name, buflen, fs->orig_name, tm);
if (l < 0 || l >= buflen)
die("Error formatting log file name: %m");
if (!fs->ls.name || strcmp(name, fs->ls.name))
{
do_log_reopen(fs, name);
switched = 1;
}
log_switch_nest--;
}
ucwlib_unlock();
return switched;
}
static void
file_close(struct log_stream *ls)
{
struct file_stream *fs = (struct file_stream *) ls;
if ((fs->flags & FF_CLOSE_FD) && fs->fd >= 0)
close(fs->fd);
xfree(fs->ls.name);
xfree(fs->orig_name);
}
static int
file_handler(struct log_stream *ls, struct log_msg *m)
{
struct file_stream *fs = (struct file_stream *) ls;
if ((fs->flags & FF_FORMAT_NAME) && m->tm)
do_log_switch(fs, m->tm);
int r = write(fs->fd, m->m, m->m_len);
return ((r < 0) ? errno : 0);
}
struct log_stream *
log_new_fd(int fd, uint flags)
{
struct log_stream *ls = log_new_stream(sizeof(struct file_stream));
struct file_stream *fs = (struct file_stream *) ls;
fs->fd = fd;
if (fd == 2)
log_stderr_replaced = 1;
fs->flags = flags;
ls->msgfmt = LSFMT_DEFAULT;
ls->handler = file_handler;
ls->close = file_close;
ls->name = xmalloc(16);
snprintf(ls->name, 16, "fd%d", fd);
return ls;
}
struct log_stream *
log_new_file(const char *path, uint flags)
{
struct log_stream *ls = log_new_stream(sizeof(struct file_stream));
struct file_stream *fs = (struct file_stream *) ls;
fs->fd = -1;
fs->flags = FF_CLOSE_FD | flags;
fs->orig_name = xstrdup(path);
if (strchr(path, '%'))
fs->flags |= FF_FORMAT_NAME;
ls->msgfmt = LSFMT_DEFAULT;
ls->handler = file_handler;
ls->close = file_close;
time_t now = time(NULL);
struct tm *tm = localtime(&now);
ASSERT(tm);
do_log_switch(fs, tm); // die()'s on errors
return ls;
}
int
log_switch(void)
{
time_t now = time(NULL);
struct tm *tm = localtime(&now);
ASSERT(tm);
int switched = 0;
for (int i=0; i < log_streams_after; i++)
if (log_streams.ptr[i]->handler == file_handler)
switched |= do_log_switch((struct file_stream *) log_streams.ptr[i], tm);
return switched;
}
void
log_switch_disable(void)
{
log_switch_nest++;
}
void
log_switch_enable(void)
{
ASSERT(log_switch_nest);
log_switch_nest--;
}
void
log_file(const char *name)
{
if (!name)
return;
log_set_default_stream(log_new_file(name, FF_FD2_FOLLOWS));
}
void
log_drop_stderr(void)
{
if (!log_stderr_replaced)
{
if (dup2(1, 2) < 0)
die("Cannot get rid of stderr: %m");
log_stderr_replaced = 1;
}
}
#ifdef TEST
int main(int argc, char **argv)
{
log_init(argv[0]);
log_file("/proc/self/fd/1");
// struct log_stream *ls = log_new_fd(1, 0);
// struct log_stream *ls = log_new_file("/tmp/quork-%Y%m%d-%H%M%S", 0);
for (int i=1; i<argc; i++)
msg(L_INFO, argv[i]);
log_close_all();
return 0;
}
#endif