syncevolution/src/syncevo/Logging.cpp

427 lines
13 KiB
C++

/*
* Copyright (C) 2009 Patrick Ohly <patrick.ohly@gmx.de>
* Copyright (C) 2009 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <syncevo/Logging.h>
#include <syncevo/LogStdout.h>
#include <syncevo/LogRedirect.h>
#include <vector>
#include <string.h>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
static RecMutex logMutex;
/**
* POD to have it initialized without relying on a constructor to run.
*/
static std::string *logProcessName;
void Logger::setProcessName(const std::string &name)
{
RecMutex::Guard guard = logMutex.lock();
if (!logProcessName) {
logProcessName = new std::string(name);
} else {
*logProcessName = name;
}
}
std::string Logger::getProcessName()
{
RecMutex::Guard guard = logMutex.lock();
return logProcessName ? *logProcessName : "";
}
RecMutex::Guard Logger::lock()
{
return logMutex.lock();
}
Logger::Logger() :
m_level(INFO)
{
}
Logger::~Logger()
{
}
/**
* Create (if necessary) and return the logger stack.
* It has at least one entry.
*
* logMutex must be locked when calling this.
*/
static std::vector<Logger::Handle> &LoggersSingleton()
{
// allocate array once and never free it because it might be needed till
// the very end of the application life cycle
static std::vector<Logger::Handle> *loggers;
if (!loggers) {
loggers = new std::vector<Logger::Handle>;
// Ensure that the array is never empty.
boost::shared_ptr<Logger> logger(new LoggerStdout);
loggers->push_back(logger);
}
return *loggers;
}
Logger::Handle Logger::instance()
{
RecMutex::Guard guard = logMutex.lock();
std::vector<Handle> &loggers = LoggersSingleton();
return loggers.back();
}
void Logger::addLogger(const Handle &logger)
{
RecMutex::Guard guard = logMutex.lock();
std::vector<Handle> &loggers = LoggersSingleton();
loggers.push_back(logger);
}
void Logger::removeLogger(Logger *logger)
{
RecMutex::Guard guard = logMutex.lock();
std::vector<Handle> &loggers = LoggersSingleton();
for (ssize_t i = loggers.size() - 1;
i >= 0;
--i) {
if (loggers[i] == logger) {
loggers[i].remove();
loggers.erase(loggers.begin() + i);
break;
}
}
}
void Logger::formatLines(Level msglevel,
Level outputlevel,
const std::string *processName,
const std::string *prefix,
const char *format,
va_list args,
boost::function<void (std::string &buffer, size_t expectedTotal)> print)
{
std::string tag;
// in case of 'SHOW' level, don't print level and prefix information
if (msglevel != SHOW) {
std::string reltime;
std::string procname;
std::string firstLine;
// Run non-blocking operations on shared data while
// holding the mutex.
{
RecMutex::Guard guard = logMutex.lock();
const std::string *realProcname;
if (processName) {
realProcname = processName;
} else {
if (!logProcessName) {
logProcessName = new std::string;
}
realProcname = logProcessName;
}
if (!realProcname->empty()) {
procname.reserve(realProcname->size() + 1);
procname += " ";
procname += *realProcname;
}
if (outputlevel >= DEBUG) {
// add relative time stamp
Timespec now = Timespec::monotonic();
if (!m_startTime) {
// first message, start counting time
m_startTime = now;
time_t nowt = time(NULL);
struct tm tm_gm, tm_local;
char buffer[2][80];
gmtime_r(&nowt, &tm_gm);
localtime_r(&nowt, &tm_local);
reltime = " 00:00:00";
strftime(buffer[0], sizeof(buffer[0]),
"%a %Y-%m-%d %H:%M:%S",
&tm_gm);
strftime(buffer[1], sizeof(buffer[1]),
"%H:%M %z %Z",
&tm_local);
std::string line =
StringPrintf("[DEBUG%s%s] %s UTC = %s\n",
procname.c_str(),
reltime.c_str(),
buffer[0],
buffer[1]);
} else {
if (now >= m_startTime) {
Timespec delta = now - m_startTime;
reltime = StringPrintf(" %02ld:%02ld:%02ld",
delta.tv_sec / (60 * 60),
(delta.tv_sec % (60 * 60)) / 60,
delta.tv_sec % 60);
} else {
reltime = " ??:??:??";
}
}
}
}
if (!firstLine.empty()) {
print(firstLine, 1);
}
if (prefix && prefix->empty()) {
prefix = NULL;
}
tag = StringPrintf("[%s%s%s] %s%s",
levelToStr(msglevel),
procname.c_str(),
reltime.c_str(),
prefix ? prefix->c_str() : "",
prefix ? ": " : "");
}
std::string output = StringPrintfV(format, args);
if (!tag.empty()) {
// Print individual lines.
//
// Total size is guessed by assuming an average line length of
// around 40 characters to predict number of lines.
size_t expectedTotal = (output.size() / 40 + 1) * tag.size() + output.size();
size_t pos = 0;
while (true) {
size_t next = output.find('\n', pos);
if (next != output.npos) {
std::string line;
line.reserve(tag.size() + next + 1 - pos);
line.append(tag);
line.append(output, pos, next + 1 - pos);
print(line, expectedTotal);
pos = next + 1;
} else {
break;
}
}
if (pos < output.size() || output.empty()) {
// handle dangling last line or empty chunk (don't
// want empty line for that, print at least the tag)
std::string line;
line.reserve(tag.size() + output.size() - pos + 1);
line.append(tag);
line.append(output, pos, output.size() - pos);
line += '\n';
print(line, expectedTotal);
}
} else {
if (!boost::ends_with(output, "\n")) {
// append newline if necessary
output += '\n';
}
print(output, 0);
}
}
Logger::MessageOptions::MessageOptions(Level level) :
m_level(level),
m_prefix(NULL),
m_file(NULL),
m_line(0),
m_function(NULL),
m_processName(NULL),
m_flags(0)
{
}
Logger::MessageOptions::MessageOptions(Level level,
const std::string *prefix,
const char *file,
int line,
const char *function,
int flags) :
m_level(level),
m_prefix((!prefix || prefix->empty()) ? NULL : prefix),
m_file(file),
m_line(line),
m_function(function),
m_processName(NULL),
m_flags(flags)
{
}
Logger::Handle::Handle() throw ()
{
}
Logger::Handle::Handle(Logger *logger) throw ()
{
m_logger.reset(logger);
}
Logger::Handle::Handle(const Handle &other) throw ()
{
m_logger = other.m_logger;
}
Logger::Handle & Logger::Handle::operator = (const Handle &other) throw ()
{
if (this != &other) {
m_logger = other.m_logger;
}
return *this;
}
Logger::Handle::~Handle() throw ()
{
m_logger.reset();
}
void Logger::Handle::message(Level level,
const std::string *prefix,
const char *file,
int line,
const char *function,
const char *format,
...)
{
va_list args;
va_start(args, format);
m_logger->messagev(MessageOptions(level, prefix, file, line, function), format, args);
va_end(args);
}
void Logger::Handle::message(Level level,
const std::string &prefix,
const char *file,
int line,
const char *function,
const char *format,
...)
{
va_list args;
va_start(args, format);
m_logger->messagev(MessageOptions(level, &prefix, file, line, function), format, args);
va_end(args);
}
void Logger::Handle::messageWithOptions(const MessageOptions &options,
const char *format,
...)
{
va_list args;
va_start(args, format);
m_logger->messagev(options, format, args);
va_end(args);
}
const char *Logger::levelToStr(Level level)
{
switch (level) {
case SHOW: return "SHOW";
case ERROR: return "ERROR";
case WARNING: return "WARNING";
case INFO: return "INFO";
case DEV: return "DEVELOPER";
case DEBUG: return "DEBUG";
default: return "???";
}
}
Logger::Level Logger::strToLevel(const char *str)
{
// order is based on a rough estimate of message frequency of the
// corresponding type
if (!str || !strcmp(str, "DEBUG")) {
return DEBUG;
} else if (!strcmp(str, "INFO")) {
return INFO;
} else if (!strcmp(str, "SHOW")) {
return SHOW;
} else if (!strcmp(str, "ERROR")) {
return ERROR;
} else if (!strcmp(str, "WARNING")) {
return WARNING;
} else if (!strcmp(str, "DEV")) {
return DEV;
} else {
return DEBUG;
}
}
#ifdef HAVE_GLIB
void Logger::glogFunc(const gchar *logDomain,
GLogLevelFlags logLevel,
const gchar *message,
gpointer userData)
{
Level level =
(logLevel & (G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL)) ? ERROR :
// glib warnings are usually not relevant for users, only for developers.
(logLevel & G_LOG_LEVEL_WARNING) ? DEV :
(logLevel & (G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO)) ? SHOW :
DEBUG;
// Downgrade some know error messages as registered with
// the LogRedirect helper class. That messages are registered
// there is a historic artifact.
if (level != DEBUG &&
LogRedirect::ignoreError(message)) {
level = DEBUG;
}
Logger::instance().message(level,
NULL,
NULL,
0,
NULL,
"%s%s%s",
logDomain ? logDomain : "",
logDomain ? ": " : "",
message);
}
#endif
int Logger::sysyncPrintf(FILE *stream,
const char *format,
...)
{
va_list args;
va_start(args, format);
static const std::string prefix("SYSYNC");
if (boost::starts_with(format, prefix) &&
format[prefix.size()] == ' ') {
// Skip initial "SYSYNC " prefix, because it will get re-added
// in a better way (= to each line) via the prefix parameter.
format += prefix.size() + 1;
}
Logger::instance().messagev(MessageOptions(DEBUG, &prefix, NULL, 0, NULL), format, args);
va_end(args);
return 0;
}
SE_END_CXX