/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
* Copyright (©) 1992,2007 Frank Tore Johansen
*
* Deliantra is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* The authors can be reached via e-mail to
*/
#include
#include
#include
#include
#include
#include "global.h"
#include "dynbuf.h"
#include "util.h"
struct logline
{
struct timeval tv;
char *buf; // includes PREFIX_LEN garbage bytes
int len;
int flags;
};
static SMUTEX(mutex);
static SCOND(cond);
static int logfd = STDERR_FILENO;
static int logsync = 1;
static std::vector > queue;
void set_logfd (int fd)
{
SMUTEX_LOCK (mutex);
logfd = fd < 0 ? STDERR_FILENO : fd;
SMUTEX_UNLOCK (mutex);
}
#define PREFIX_LEN sizeof ("0000-00-00 00:00:00.0000+") - 1
static void
log_sync (logline &line)
{
struct tm lt;
char pfx [PREFIX_LEN];
localtime_r (&line.tv.tv_sec, <);
sprintf (pfx, "%04d-%02d-%02d %02d:%02d:%02d.%04d",
lt.tm_year + 1900,
lt.tm_mon + 1,
lt.tm_mday,
lt.tm_hour,
lt.tm_min,
lt.tm_sec,
(int)(line.tv.tv_usec / 100)
);
pfx [PREFIX_LEN - 1] = line.flags & logSync ? '=' : ' ';
struct iovec iov [2];
iov [0].iov_base = pfx;
iov [0].iov_len = PREFIX_LEN;
char *buf = line.buf;
while (char *end = strchr (buf, '\n'))
{
iov [1].iov_base = buf;
iov [1].iov_len = end - buf + 1;
writev (STDERR_FILENO, iov, 2);
if (logfd != STDERR_FILENO)
writev (logfd, iov, 2);
buf = end + 1;
if (buf == line.buf + line.len)
break;
pfx [PREFIX_LEN - 1] = '+';
}
sfree (line.buf, line.len);
}
static void *
logthread_proc (void *arg)
{
int idx = 0;
for (;;)
{
logline line;
SMUTEX_LOCK (mutex);
while (queue.empty ())
SCOND_WAIT (cond, mutex);
line = queue [idx++];
// this algorithm could result in an ever-increasing vector
// size if we log faster than we can write, but if that happens
// we have bigger problems.
if (idx == queue.size ())
{
queue.clear ();
idx = 0;
}
SMUTEX_UNLOCK (mutex);
log_sync (line);
}
}
void
log_cleanup ()
{
logsync = 1;
for (;;)
{
int done;
SMUTEX_LOCK (mutex);
done = queue.empty ();
SMUTEX_UNLOCK (mutex);
if (done)
break;
usleep (10000);
}
}
static void
af_child ()
{
logsync = 1;
}
static struct logthread : thread
{
logthread ()
{
pthread_atfork (0, 0, af_child);
start (logthread_proc);
logsync = 0;
}
} logthread;
void
LOG (int flags, const char *format, ...)
{
int level = flags & 15;
if (level > settings.debug)
return;
logline line;
gettimeofday (&line.tv, 0);
static dynbuf_text buf;
va_list ap;
va_start (ap, format);
buf.vprintf (format, ap);
va_end (ap);
buf << '\n';
line.buf = buf.linearise ();
line.len = buf.size ();
if (line.buf [line.len - 2] == '\n')
--line.len;
line.buf = salloc (line.len, line.buf);
buf.clear ();
if (logsync)
flags |= logSync;
if (flags & logBacktrace)
{
line.buf [line.len - 1] = 0;
log_backtrace (line.buf);
line.buf [line.len - 1] = '\n';
}
line.flags = flags;
if (line.flags & logSync)
log_sync (line);
else
{
SMUTEX_LOCK (mutex);
queue.push_back (line);
SMUTEX_UNLOCK (mutex);
SCOND_SIGNAL (cond);
}
}