// ----------------------------------------------------------------------
// File: Logging.cc
// Author: Andreas-Joachim Peters - CERN
// ----------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2011 CERN/Switzerland *
* *
* This program 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 .*
************************************************************************/
#include "common/Namespace.hh"
#include "common/Logging.hh"
#include "XrdSys/XrdSysPthread.hh"
#include
#include
#include
EOSCOMMONNAMESPACE_BEGIN
static std::atomic sCounter {0};
static typename std::aligned_storage::type
logging_buf; ///< Memory for the global logging object
Logging& gLogging = reinterpret_cast(logging_buf);
//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
LoggingInitializer::LoggingInitializer()
{
if (sCounter++ == 0) {
new(&gLogging) Logging(); // placement new
}
}
//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
LoggingInitializer::~LoggingInitializer()
{
if (--sCounter == 0) {
gLogging.LB->shutDown();
(&gLogging)->~Logging();
}
}
//------------------------------------------------------------------------------
// Get singleton instance
//------------------------------------------------------------------------------
Logging&
Logging::GetInstance()
{
return gLogging;
}
//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
Logging::Logging():
gLogMask(0), gPriorityLevel(0), gToSysLog(false), gUnit("none"),
gShortFormat(0), gRateLimiter(false)
{
LB = new LogBuffer;
// Initialize the log array and sets the log circular size
gLogCircularIndex.resize(LOG_DEBUG + 1);
gLogMemory.resize(LOG_DEBUG + 1);
gCircularIndexSize = EOSCOMMONLOGGING_CIRCULARINDEXSIZE;
for (int i = 0; i <= LOG_DEBUG; i++) {
gLogCircularIndex[i] = 0;
gLogMemory[i].resize(gCircularIndexSize);
}
gZeroVid.name = "-";
XrdOucString tosyslog;
if (getenv("EOS_LOG_SYSLOG")) {
tosyslog = getenv("EOS_LOG_SYSLOG");
if ((tosyslog == "1" ||
(tosyslog == "true"))) {
gToSysLog = true;
}
}
}
//------------------------------------------------------------------------------
// Should log function
//------------------------------------------------------------------------------
bool
Logging::shouldlog(const char* func, int priority)
{
if (priority == LOG_SILENT) {
return true;
}
// short cut if log messages are masked
if (!((LOG_MASK(priority) & gLogMask))) {
return false;
}
// apply filter to avoid message flooding for debug messages
if (priority >= LOG_INFO) {
if (gDenyFilter.Num()) {
// this is a normal filter by function name
if (gDenyFilter.Find(func)) {
return false;
}
}
}
return true;
}
#if LOG_BUFFER_DBG
static int cmpPtr(const void * a, const void * b) {
return ( *(intptr_t*)a - *(intptr_t*)b );
}
static void
check_log_buffer_chain(LogBuffer::log_buffer *chain, int maxSize, const char *name, const char *_FILE, const int _LINE) {
LogBuffer::log_buffer *buff2, *buff3;
LogBuffer::log_buffer **arrAct = (LogBuffer::log_buffer **) malloc(maxSize * sizeof(void *));
int m;
for (m = 0, buff2 = chain; buff2 != NULL; buff2 = buff2->h.next, m++) {
if (m > 0) {
if (buff3 == buff2) {
fprintf(stderr,
"%s:%d log_buffer_prb buffer twice (in succesion) on %s chain after %u items, cut\n",
_FILE, _LINE, name, m);
fflush(stderr);
buff3->h.next = NULL;
}
};
buff3 = buff2;
if (m >= maxSize) {
fprintf(stderr,
"%s:%d log_buffer_prb more (%u) buffers on %s list than total (%u), cut\n",
_FILE, _LINE, m, name, maxSize);
fflush(stderr);
buff3->h.next = NULL;
break;
}
arrAct[m] = buff2;
}
if (m > 1) {
qsort(arrAct, m, sizeof(void *), cmpPtr);
int i;
for (i=1; i < m; i++)
if (arrAct[i-1] == arrAct[i]) {
buff2 = arrAct[i-1];
fprintf(stderr,
"%s:%d log_buffer_prb buffer %p twice on chain %s, cut\n", _FILE, _LINE,
buff2, name);
fflush(stderr);
buff2->h.next = NULL;
}
}
free(arrAct);
fprintf(stderr, "%s:%d log_buffer chain %s verified %u elements\n",
_FILE, _LINE, name, m);
}
#endif
LogBuffer::log_buffer *
LogBuffer::log_alloc_buffer() {
LogBuffer::log_buffer *buff = NULL;
std::unique_lock guard(log_buffer_mutex);
if (shuttingDown) return NULL;
/* log_buffer_balance is incorrect until we really allocated a buffer! */
LogBuffer::log_buffer_balance++;
while (true) {
buff = free_buffers;
if (buff != NULL) {
free_buffers = buff->h.next;
log_buffer_free--;
break;
}
if (shuttingDown) return NULL;
/* no free buffer, alloc new one if below budget, or wait */
if ( LogBuffer::log_buffer_total < LogBuffer::max_log_buffers ) {
buff = (struct log_buffer *) malloc(sizeof(struct log_buffer));
log_buffer_total++;
#if LOG_BUFFER_DBG > 2
buff->h.debug1 = 7;
{
/* consistency checks, to be removed*/
int num_in_queue;
LogBuffer::log_buffer *bx = active_head, *bbx;
for (num_in_queue=0; bx != NULL; num_in_queue++) {
bbx = bx->h.next;
if (bx == bbx) {
fprintf(stderr, "%s:%d active log_buffer_prb!\n",
__FILE__, __LINE__);
bx->h.next = NULL;
break;
}
bx = bbx;
}
if (num_in_queue != log_buffer_in_q)
fprintf(stderr, "%s:%d wrong log_buffer_in_q: %d != %d\n",
__FILE__, __LINE__,
num_in_queue, log_buffer_in_q);
fprintf(stderr,
"\ntotal_log_buffers: %d balance %d in_q %d free %d waiters %d\n",
log_buffer_total, log_buffer_balance,
log_buffer_in_q,
log_buffer_free,
log_buffer_waiters);
}
#else
if ((log_buffer_total & 0x1ff) == 0)
fprintf(stderr,
"\ntotal_log_buffers: %d balance %d in_q %d free %d waiters %d\n",
log_buffer_total, log_buffer_balance,
log_buffer_in_q, log_buffer_free, log_buffer_waiters);
#endif
break;
}
/* wait for a free buffer */
#if LOG_BUFFER_DBG > 2
check_log_buffer_chain(active_head, log_buffer_total,
"active", __FILE__, __LINE__);
#endif
if ((log_buffer_num_waits & 0xfff) == 0)
fprintf(stderr,
"log_buffer_shortage #%u with %u waiters, total_log_buffers %u balance %d in_q %u free %u\n",
log_buffer_num_waits, log_buffer_waiters,
LogBuffer::log_buffer_total, LogBuffer::log_buffer_balance,
log_buffer_in_q, log_buffer_free);
log_buffer_num_waits++;
LogBuffer::log_buffer_waiters++; /* this asks for a wake-up call when a buffer is freed */
log_buffer_shortage.wait(guard);
LogBuffer::log_buffer_waiters--;
/* retry... */
continue;
}
buff->h.next = NULL;
buff->h.fanOutBuffer = NULL;
#if LOG_BUFFER_DBG
buff->h.debug1 = 42;;
#endif
return buff;
}
void
LogBuffer::log_return_buffers(LogBuffer::log_buffer *buff) {
LogBuffer::log_buffer *buff2, *buff3;
/* count number of buffers returned, find last one (buff2 (='previous')) */
int n = 1;
for (buff2 = buff; (buff3 = buff2->h.next) != NULL; buff2 = buff3) {
#if LOG_BUFFER_DBG > 1
if (buff3->h.debug1 != 42) { /* check the next buffer */
fprintf(stderr,
"%s:%d log_buffer_prb returning circular buffer list %p->%p code %d, cut\n",
__FILE__, __LINE__, buff2, buff3, buff3->h.debug1);
buff2->h.next = NULL;
break;
} else /*debug*/
#endif
#if LOG_BUFFER_DBG > 0
buff2->h.debug1 = 52; /* flag buffer as seen */
#endif
n++;
}
std::unique_lock guard(log_buffer_mutex);
#if LOG_BUFFER_DBG > 0
buff2->h.debug1 = 52; /* flags the last buffer as well, it has been checked */
if (log_buffer_free + n > log_buffer_total) { /* Something's wrong, check all chains thoroughly */
fprintf(stderr, "%s:%d log_buffer_prb log_buffer_free %d > log_buffer_total %d, %d buffers returned\n",
__FILE__, __LINE__, log_buffer_free, log_buffer_total, n);
fflush(stderr);
if (n > 1)
check_log_buffer_chain(buff, n+1, "2Bfreed", __FILE__, __LINE__);
check_log_buffer_chain(active_head, log_buffer_total, "active", __FILE__, __LINE__);
return; /* drop all these buffers */
}
#endif
buff2->h.next = free_buffers;
free_buffers = buff;
log_buffer_free += n;
if (log_buffer_waiters > 0) { /* This is the condition the CV protects */
if (n == 1)
log_buffer_shortage.notify_one();
else
log_buffer_shortage.notify_all();
}
}
void
LogBuffer::log_queue_buffer(LogBuffer::log_buffer *buff) {
std::unique_lock guard(log_buffer_mutex);
if (shuttingDown) { /* get out quickly, the queues are no longer valid */
// free(buff);
return;
}
/* this starts the log thread */
if ((not log_thread_started) and (not log_suspended))
resume_int();
LogBuffer::log_buffer *prev;
if (active_tail == NULL) {
/* the following works because offset(next) == 0 */
prev = (LogBuffer::log_buffer *) &active_tail;
} else
prev = active_tail;
buff->h.next = NULL;
prev->h.next = buff;
active_tail = buff;
if (!active_head) active_head = buff;
log_buffer_in_q++;
/* log_buffer_balance designates buffers between intended allocation and print queueing */
log_buffer_balance--;
log_buffer_cond.notify_all();
}
void
LogBuffer::log_thread() {
LogBuffer::log_buffer *buff = NULL, *buff_2b_returned=NULL;
unsigned int num_buff_2b_returned = 0;
#if LOG_BUFFER_DBG
unsigned int notify_counter = 0;
unsigned int old_waits = 0;
#endif
std::unique_lock guard(log_buffer_mutex);
while(1) {
if ( shuttingDown > 0 or active_head == NULL or num_buff_2b_returned > 15 or log_buffer_waiters > 0 ) {
if (buff_2b_returned != NULL) {
guard.unlock();
log_return_buffers(buff_2b_returned);
guard.lock();
num_buff_2b_returned = 0;
buff_2b_returned = NULL;
continue;
}
if (shuttingDown > 0 and (active_head == NULL or shuttingDown > 3)) {
/* there is no safe way to dispatch what's still in the queue: the stream pointers
* may no longer be valid unless this is a graceful shutdown */
shuttingDown = 42;
guard.unlock(); /* Time to get out */
return;
}
if (active_head == NULL) {
#if LOG_BUFFER_DBG
if ((log_buffer_num_waits > old_waits) and ++notify_counter > 1000) {
notify_counter = 0;
old_waits = log_buffer_num_waits;
fprintf(stderr, "\nlog_buffer queue empty, log_buffer_total: %d balance %d free %d waits %u waiters %d\n",
log_buffer_total, log_buffer_balance, log_buffer_free,
log_buffer_num_waits, log_buffer_waiters);
}
#endif
fflush(stderr);
log_buffer_cond.wait(guard);
if (shuttingDown) {
shuttingDown = 41;
continue;
}
}
}
if (active_head) {
/* unchain */
buff = active_head;
active_head = active_head->h.next;
bool null_active_head = active_head == NULL;
if (null_active_head) {
active_tail = NULL;
}
log_buffer_in_q--;
guard.unlock(); /* drop while buffer is printed */
fprintf(stderr, "%s\n", buff->buffer);
if (null_active_head) {
fflush(stderr); /* only flush if there's no other */
}
if (eos::common::Logging::GetInstance().gToSysLog) {
syslog(buff->h.priority, "%s", buff->h.ptr);
}
if (buff->h.fanOutBuffer != NULL) {
if (buff->h.fanOutS != NULL) {
fputs(buff->h.fanOutBuffer, buff->h.fanOutS);
fflush(buff->h.fanOutS);
}
if (buff->h.fanOut != NULL) {
fputs(buff->h.fanOutBuffer, buff->h.fanOut);
fflush(buff->h.fanOut);
}
}
if (buff_2b_returned != buff) {
buff->h.next = buff_2b_returned;
buff_2b_returned = buff;
num_buff_2b_returned++;
} else
fprintf(stderr, "%s.%d log_buffer_prb returning returned log_buffer\n",
__FILE__, __LINE__);
guard.lock();
}
}
}
//------------------------------------------------------------------------------
// Logging function
//------------------------------------------------------------------------------
const char*
Logging::log(const char* func, const char* file, int line, const char* logid,
const VirtualIdentity& vid, const char* cident, int priority,
const char* msg, ...)
{
bool silent = (priority == LOG_SILENT);
// short cut if log messages are masked
if (!silent && !((LOG_MASK(priority) & gLogMask))) {
return "";
}
// apply filter to avoid message flooding for debug messages
if (!silent && priority >= LOG_INFO) {
if (gAllowFilter.Num()) {
// if this is a pass-through filter e.g. we want to see exactly this messages
if (!gAllowFilter.Find(func)) {
return "";
}
} else if (gDenyFilter.Num()) {
// this is a normal filter by function name
if (gDenyFilter.Find(func)) {
return "";
}
}
}
struct LogBuffer::log_buffer *logBuffer = LB->log_alloc_buffer();
if (logBuffer == NULL) return ""; /* log object being destroyed */
char* buffer = logBuffer->buffer;
XrdOucString File = file;
// we show only one hierarchy directory like Acl (assuming that we have only
// file names like *.cc and *.hh
File.erase(0, File.rfind("/") + 1);
File.erase(File.length() - 3);
time_t current_time;
struct timeval tv;
struct timezone tz;
tm tm;
va_list args;
va_start(args, msg);
gettimeofday(&tv, &tz);
current_time = tv.tv_sec;
char linen[16];
sprintf(linen, "%d", line);
char fcident[1024];
XrdOucString truncname = vid.name;
// we show only the last 16 bytes of the name
if (truncname.length() > 16) {
truncname.insert("..", 0);
truncname.erase(0, truncname.length() - 16);
}
char sourceline[64];
if (gShortFormat) {
localtime_r(¤t_time, &tm);
snprintf(sourceline, sizeof(sourceline) - 1, "%s:%s", File.c_str(), linen);
XrdOucString slog = logid;
if (slog.beginswith("logid:")) {
slog.erase(0, 6);
sprintf(buffer,
"%02d%02d%02d %02d:%02d:%02d t=%lu.%06lu f=%-16s l=%s %s s=%-24s ",
tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
tm.tm_min, tm.tm_sec, current_time, (unsigned long) tv.tv_usec,
func, GetPriorityString(priority), slog.c_str(), sourceline);
} else {
sprintf(buffer,
"%02d%02d%02d %02d:%02d:%02d t=%lu.%06lu f=%-16s l=%s tid=%016lx s=%-24s ",
tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
tm.tm_min, tm.tm_sec, current_time, (unsigned long) tv.tv_usec,
func, GetPriorityString(priority), (unsigned long) XrdSysThread::ID(),
sourceline);
}
} else {
sprintf(fcident, "tident=%s sec=%-5s uid=%d gid=%d name=%s geo=\"%s\"", cident,
vid.prot.c_str(), vid.uid, vid.gid, truncname.c_str(), vid.geolocation.c_str());
localtime_r(¤t_time, &tm);
snprintf(sourceline, sizeof(sourceline) - 1, "%s:%s", File.c_str(), linen);
sprintf(buffer,
"%02d%02d%02d %02d:%02d:%02d time=%lu.%06lu func=%-24s level=%s logid=%s unit=%s tid=%016lx source=%-30s %s ",
tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
tm.tm_sec, current_time, (unsigned long) tv.tv_usec, func,
GetPriorityString(priority), logid, gUnit.c_str(),
(unsigned long) XrdSysThread::ID(), sourceline, fcident);
}
char* ptr = buffer + strlen(buffer);
// limit the length of the output to buffer-1 length
vsnprintf(ptr, sizeof(logBuffer->buffer) - (ptr - buffer + 1), msg, args);
if (!silent) {
XrdSysMutexHelper scope_lock(gMutex);
if (rate_limit(tv, priority, file, line)) {
LB->log_return_buffers(logBuffer);
va_end(args);
return "";
}
}
logBuffer->h.ptr = ptr;
if (!silent) {
if (gLogFanOut.size()) {
logBuffer->h.fanOutS = NULL;
logBuffer->h.fanOut = NULL;
logBuffer->h.fanOutBuffer = ptr + strlen(ptr) + 1;
logBuffer->h.fanOutBufLen = sizeof(logBuffer->buffer) - (logBuffer->h.fanOutBuffer-logBuffer->buffer);
// we do log-message fanout
if (gLogFanOut.count("*")) {
logBuffer->h.fanOutS = gLogFanOut["*"];
snprintf(logBuffer->h.fanOutBuffer, logBuffer->h.fanOutBufLen, "%s\n", logBuffer->buffer);
}
if (gLogFanOut.count(File.c_str())) {
logBuffer->buffer[15] = 0;
logBuffer->h.fanOut = gLogFanOut[File.c_str()];
snprintf(logBuffer->h.fanOutBuffer, logBuffer->h.fanOutBufLen,
"%s %s%s%s %-30s %s \n",
logBuffer->buffer,
GetLogColour(GetPriorityString(priority)),
GetPriorityString(priority),
EOS_TEXTNORMAL,
sourceline,
logBuffer->h.ptr); /* truncation not an issue */
logBuffer->buffer[15] = ' ';
} else {
if (gLogFanOut.count("#")) {
logBuffer->buffer[15] = 0;
logBuffer->h.fanOut = gLogFanOut["#"];
snprintf(logBuffer->h.fanOutBuffer, logBuffer->h.fanOutBufLen,
"%s %s%s%s [%05d/%05d] %16s ::%-16s %s \n",
logBuffer->buffer,
GetLogColour(GetPriorityString(priority)),
GetPriorityString(priority),
EOS_TEXTNORMAL,
vid.uid,
vid.gid,
truncname.c_str(),
func,
logBuffer->h.ptr
);
logBuffer->buffer[15] = ' ';
}
}
}
}
va_end(args);
const char* rptr;
if (silent) {
priority = LOG_DEBUG;
}
// store into global log memory
{
XrdSysMutexHelper scope_lock(gMutex);
/* the following copies the buffer, hence it can be queued and vanish anytime after */
gLogMemory[priority][(gLogCircularIndex[priority]) % gCircularIndexSize] =
buffer;
rptr = gLogMemory[priority][(gLogCircularIndex[priority]) %
gCircularIndexSize].c_str();
gLogCircularIndex[priority]++;
}
if (!silent) {
logBuffer->h.priority = priority;
LB->log_queue_buffer(logBuffer);
} else {
LB->log_return_buffers(logBuffer);
}
return rptr;
}
bool
Logging::rate_limit(struct timeval& tv, int priority, const char* file,
int line)
{
static bool do_limit = false;
static std::string last_file = "";
static int last_line = 0;
static int last_priority = priority;
static struct timeval last_tv;
if (!gRateLimiter) {
return false;
}
if ((line == last_line) &&
(priority == last_priority) &&
(last_file == file) &&
(priority < LOG_WARNING)) {
float elapsed = (1.0 * (tv.tv_sec - last_tv.tv_sec)) - ((
tv.tv_usec - last_tv.tv_usec) / 1000000.0);
if (elapsed < 5.0) {
if (!do_limit) {
fprintf(stderr,
" ---- high rate error messages suppressed ----\n");
}
do_limit = true;
} else {
do_limit = false;
}
} else {
do_limit = false;
}
if (!do_limit) {
last_tv = tv;
last_line = line;
last_file = file;
last_priority = priority;
}
return do_limit;
}
EOSCOMMONNAMESPACE_END