/*
* UCW Library -- Logging to Files
*
- * (c) 1997--2009 Martin Mares <mj@ucw.cz>
+ * (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/lfs.h"
-#include "ucw/threads.h"
-#include "ucw/simple-lists.h"
+#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>
-/*
- * Use of the private fields of struct log_stream:
- *
- * idata file descriptor
- * udata various flags (FF_xxx)
- * pdata original name with strftime escapes
- * name current name of the log file
- * (a dynamically allocated buffer)
- */
-
-enum log_file_flag {
- FF_FORMAT_NAME = 1, // Name contains strftime escapes
- FF_CLOSE_FD = 2, // Close the fd with the stream
+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 log_stream *ls, const char *name)
+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 (ls->idata < 0)
- ls->idata = fd;
- else
+ if (fs->fd >= 0)
+ close(fs->fd);
+ fs->fd = fd;
+ if (fs->flags & FF_FD2_FOLLOWS)
{
- dup2(fd, ls->idata);
- close(fd);
+ dup2(fd, 2);
+ log_stderr_replaced = 1;
}
- if (ls->name)
+ if (fs->ls.name)
{
- xfree(ls->name);
- ls->name = NULL; // We have to keep this consistent, die() below can invoke logging
+ xfree(fs->ls.name);
+ fs->ls.name = NULL; // We have to keep the stream consistent -- die() below can invoke logging
}
- ls->name = xstrdup(name);
+ fs->ls.name = xstrdup(name);
}
static int
-do_log_switch(struct log_stream *ls, struct tm *tm)
+do_log_switch(struct file_stream *fs, struct tm *tm)
{
- if (!(ls->udata & FF_FORMAT_NAME))
+ if (!(fs->flags & FF_FORMAT_NAME))
{
- if (ls->idata >= 0)
+ if (fs->fd >= 0)
return 1;
else
{
- do_log_reopen(ls, ls->pdata);
+ do_log_reopen(fs, fs->orig_name);
return 1;
}
}
- int buflen = strlen(ls->pdata) + MAX_EXPAND;
+ int buflen = strlen(fs->orig_name) + MAX_EXPAND;
char name[buflen];
int switched = 0;
if (!log_switch_nest) // Avoid infinite loops if we die when switching logs
{
log_switch_nest++;
- int l = strftime(name, buflen, ls->pdata, tm);
+ int l = strftime(name, buflen, fs->orig_name, tm);
if (l < 0 || l >= buflen)
die("Error formatting log file name: %m");
- if (!ls->name || strcmp(name, ls->name))
+ if (!fs->ls.name || strcmp(name, fs->ls.name))
{
- do_log_reopen(ls, name);
+ do_log_reopen(fs, name);
switched = 1;
}
log_switch_nest--;
return switched;
}
-/* Emulate the old single-file interface: close the existing log file and open a new one. */
-void
-log_file(const char *name)
-{
- if (!name)
- return;
-
- struct log_stream *ls = log_new_file(name);
- struct log_stream *def = log_stream_by_flags(0);
- simp_node *s;
- while (s = clist_head(&def->substreams))
- {
- struct log_stream *old = s->p;
- log_rm_substream(def, old);
- if (old != LOG_STREAM_DEFAULT)
- log_close_stream(old);
- }
- dup2(ls->idata, 2); // Let fd2 be an alias for the log file
- log_add_substream(def, ls);
-}
-
-/* destructor for standard files */
static void
file_close(struct log_stream *ls)
{
- if ((ls->udata & FF_CLOSE_FD) && ls->idata >= 0)
- close(ls->idata);
- xfree(ls->name);
- xfree(ls->pdata);
+ 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);
}
-/* handler for standard files */
static int
file_handler(struct log_stream *ls, struct log_msg *m)
{
- if ((ls->udata & FF_FORMAT_NAME) && m->tm)
- do_log_switch(ls, m->tm);
+ 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(ls->idata, m->m, m->m_len);
- /* FIXME: check for errors here? */
- return 0;
+ int r = write(fs->fd, m->m, m->m_len);
+ return ((r < 0) ? errno : 0);
}
-/* assign log to a file descriptor */
-/* initialize with the default formatting, does NOT close the descriptor */
struct log_stream *
-log_new_fd(int fd)
+log_new_fd(int fd, uint flags)
{
- struct log_stream *ls = log_new_stream();
- ls->idata = fd;
+ 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;
return ls;
}
-/* open() a file (append mode) */
-/* initialize with the default formatting */
struct log_stream *
-log_new_file(const char *path)
+log_new_file(const char *path, uint flags)
{
- struct log_stream *ls = log_new_stream();
- ls->idata = -1;
- ls->pdata = xstrdup(path);
+ 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, '%'))
- ls->udata = FF_FORMAT_NAME;
- ls->udata |= FF_CLOSE_FD;
+ 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(ls, tm); // die()'s on errors
+ do_log_switch(fs, tm); // die()'s on errors
return ls;
}
int switched = 0;
for (int i=0; i < log_streams_after; i++)
if (log_streams.ptr[i]->handler == file_handler)
- switched |= do_log_switch(log_streams.ptr[i], tm);
+ switched |= do_log_switch((struct file_stream *) log_streams.ptr[i], tm);
return switched;
}
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);
- // struct log_stream *ls = log_new_file("/tmp/quork-%Y%m%d-%H%M%S");
+ // 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;
}