/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2017,2018 Marc Alexander Lehmann / the Deliantra team
* Copyright (©) 2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
*
* Deliantra is free software: you can redistribute it and/or modify it under
* the terms of the Affero 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 Affero GNU General Public License
* and 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;
};
typedef std::vector > logvector;
static SMUTEX(mutex);
static SMUTEX(fdlock);
static SCOND(cond);
static int logfd = STDERR_FILENO;
static int logsync = 1;
static logvector queue;
int log_setfd (int fd)
{
SMUTEX_LOCK (fdlock);
int old = logfd;
logfd = fd < 0 ? STDERR_FILENO : fd;
SMUTEX_UNLOCK (fdlock);
return old;
}
#define PREFIX_LEN sizeof ("0000-00-00 00:00:00.0000 L+") - 1
static void
log_sync (logline &line)
{
static const char levelchar [16+1] = "EWIDt???????????";
struct tm lt;
char pfx [PREFIX_LEN];
localtime_r (&line.tv.tv_sec, <);
sprintf (pfx, "%04d-%02d-%02d %02d:%02d:%02d.%04d %c",
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),
levelchar [line.flags & 15]
);
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;
if (logsync != 2)
SMUTEX_LOCK (fdlock);
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] = '+';
}
if (logsync != 2)
SMUTEX_UNLOCK (fdlock);
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 ())
{
if (idx < 32)
queue.clear ();
else
logvector ().swap (queue); // free memory, hopefully
idx = 0;
}
SMUTEX_UNLOCK (mutex);
log_sync (line);
}
}
void
log_cleanup ()
{
logsync = 1;
SMUTEX_UNLOCK (fdlock);
for (;;)
{
int done;
SMUTEX_LOCK (mutex);
done = queue.empty ();
SMUTEX_UNLOCK (mutex);
if (done)
break;
usleep (10000);
SMUTEX_LOCK (fdlock);
SMUTEX_UNLOCK (fdlock);
}
}
static void
af_child ()
{
logsync = 2;
}
static struct logthread : thread
{
logthread ()
{
pthread_atfork (0, 0, af_child);
start (logthread_proc);
logsync = 0;
}
} logthread;
void
LOG (int flags, const_utf8_string 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);
}
}
static int suspended;
void
log_suspend ()
{
if (!suspended++)
{
LOG (llevDebug, "logging suspended.");
SMUTEX_LOCK (fdlock);
}
}
void
log_resume ()
{
if (!--suspended)
{
SMUTEX_UNLOCK (fdlock);
LOG (llevDebug, "logging resumed.");
}
}