syncevolution/src/syncevo/Logging.h

465 lines
15 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
*/
#ifndef INCL_LOGGING
#define INCL_LOGGING
#include <stdarg.h>
#include <stdio.h>
#include <string>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef HAVE_GLIB
# include <glib.h>
#endif
#include <syncevo/Timespec.h>
#include <syncevo/ThreadSupport.h>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
/**
* Abstract interface for logging in SyncEvolution. Can be
* implemented by other classes to add information (like a certain
* prefix) before passing the message on to a global instance for the
* actual processing.
*
* The static methods provide some common utility code and manage a
* global stack of loggers. The one pushed latest is called first to
* handle a new message. It can find its parent logger (= the one
* added just before it) and optionally pass the message up the chain
* before or after processing it itself.
*
* All methods must be thread-safe.
*/
class Logger
{
public:
/**
* Which of these levels is the right one for a certain message
* is a somewhat subjective choice. Here is a definition how they
* are supposed to be used:
* - error: severe problem which the user and developer have to
* know about
* - warning: a problem that was handled, but users and developers
* probably will want to know about
* - info: information about a sync session which the user
* will want to read during/after each sync session
* - developer: information about a sync session that is not
* interesting for a user (for example, because it
* is constant and already known) but which should
* be in each log because developers need to know
* it. Messages logged with this calls will be included
* at LOG_LEVEL_INFO, therefore messages should be small and
* not recur so that the log file size remains small.
* - debug: most detailed logging, messages may be arbitrarily large
*
* Here is a decision tree which helps to pick the right level:
* - an error: => ERROR
* - a non-fatal error: => WARNING
* - it changes during each sync or marks important steps
* in the sync: INFO
* - same as before, but without the [INFO] prefix added to each line: => SHOW
* - small, non-recurring message which is important for developers
* who read a log produced at LOG_LEVEL_INFO: DEVELOPER
* - everything else: DEBUG
*/
typedef enum {
/**
* no error messages printed
*/
NONE = -1,
/**
* only error messages printed
*/
ERROR,
/**
* error and warning messages printed
*/
WARNING,
/**
* "Normal" stdout output which is meant to be seen by a
* user.
*/
SHOW,
/**
* errors and info messages for users and developers will be
* printed: use this to keep the output consise and small
*/
INFO,
/**
* important messages to developers
*/
DEV,
/**
* all messages will be printed, including detailed debug
* messages
*/
DEBUG
} Level;
static const char *levelToStr(Level level);
/** always returns a valid level, also for NULL, by falling back to DEBUG */
static Level strToLevel(const char *str);
/**
* additional, short string identifying the SyncEvolution process;
* empty if master process
*
* Included by LoggerStdout in the [INFO/DEBUG/...] tag.
*/
static void setProcessName(const std::string &name);
static std::string getProcessName();
/**
* Obtains the recursive logging mutex.
*
* All calls offered by the Logger class already lock that mutex
* internally, but sometimes it may be necessary to protect a larger
* region of logging related activity.
*/
static RecMutex::Guard lock();
#ifdef HAVE_GLIB
/**
* can be used in g_log_set_handler() to redirect log messages
* into our own logging; must be called for each log domain
* that may be relevant
*/
static void glogFunc(const gchar *logDomain,
GLogLevelFlags logLevel,
const gchar *message,
gpointer userData);
#endif
/**
* can be used as replacement for libsynthesis console printf function,
* logs at DEBUG level
*
* @param stream is ignored
* @param format guaranteed to start with "SYSYNC "
* @return always 0
*/
static int sysyncPrintf(FILE *stream,
const char *format,
...);
Logger();
virtual ~Logger();
/**
* Prepare logger for removal from logging stack. May be called
* multiple times.
*
* The logger should stop doing anything right away and just pass
* on messages until it gets deleted eventually.
*/
virtual void remove() throw () {}
/**
* Collects all the parameters which may get passed to
* messagev.
*/
class MessageOptions {
public:
/** level for current message */
Level m_level;
/** inserted at beginning of each line, if non-NULL */
const std::string *m_prefix;
/** source file where message comes from, if non-NULL */
const char *m_file;
/** source line number, if file is non-NULL */
int m_line;
/** surrounding function name, if non-NULL */
const char *m_function;
/** name of the process which originally created the message, if different from current one */
const std::string *m_processName;
/** additional flags */
int m_flags;
enum {
/**
* The message was written into a global log (syslog, dlt, ...)
* already. Such a message must not be logged again.
*/
ALREADY_LOGGED = 1<<0,
/**
* The message must be written into a global log,
* but not to stdout.
*/
ONLY_GLOBAL_LOG = 1<<1
};
MessageOptions(Level level);
MessageOptions(Level level,
const std::string *prefix,
const char *file,
int line,
const char *function,
int flags = 0);
};
/**
* output a single message
*
* @param options carries additional information about the message
* @param format sprintf format
* @param args parameters for sprintf: consumed by this function,
* make copy with va_copy() if necessary!
*/
virtual void messagev(const MessageOptions &options,
const char *format,
va_list args) = 0;
/**
* A convenience and backwards-compatibility class which allows
* calling some methods of the underlying pointer directly similar
* to the Logger reference returned in previous SyncEvolution
* releases.
*/
class Handle
{
boost::shared_ptr<Logger> m_logger;
public:
Handle() throw ();
Handle(Logger *logger) throw ();
template<class L> Handle(const boost::shared_ptr<L> &logger) throw () : m_logger(logger) {}
template<class L> Handle(const boost::weak_ptr<L> &logger) throw () : m_logger(logger.lock()) {}
Handle(const Handle &other) throw ();
Handle &operator = (const Handle &other) throw ();
~Handle() throw ();
operator bool () const { return m_logger; }
bool operator == (Logger *logger) const { return m_logger.get() == logger; }
Logger *get() const { return m_logger.get(); }
void messagev(const MessageOptions &options,
const char *format,
va_list args)
{
m_logger->messagev(options, format, args);
}
void message(Level level,
const std::string *prefix,
const char *file,
int line,
const char *function,
const char *format,
...)
#ifdef __GNUC__
__attribute__((format(printf, 7, 8)))
#endif
;
void message(Level level,
const std::string &prefix,
const char *file,
int line,
const char *function,
const char *format,
...)
#ifdef __GNUC__
__attribute__((format(printf, 7, 8)))
#endif
;
void messageWithOptions(const MessageOptions &options,
const char *format,
...)
#ifdef __GNUC__
__attribute__((format(printf, 3, 4)))
#endif
;
void setLevel(Level level) { m_logger->setLevel(level); }
Level getLevel() { return m_logger->getLevel(); }
void remove() throw () { m_logger->remove(); }
};
/**
* Grants access to the singleton which implements logging.
* The implementation of this function and thus the Log
* class itself is platform specific: if no Log instance
* has been set yet, then this call has to create one.
*/
static Handle instance();
/**
* Overrides the current default Logger implementation.
*
* @param logger will be used for all future logging activities
*/
static void addLogger(const Handle &logger);
/**
* Remove the specified logger.
*
* Note that the logger might still be in use afterwards, for
* example when a different thread currently uses it. Therefore
* loggers should be small stub classes. If they need access to
* more expensive classes to do their work, they shold hold weak
* reference to those and only lock them when logging.
*/
static void removeLogger(Logger *logger);
virtual void setLevel(Level level) { m_level = level; }
virtual Level getLevel() { return m_level; }
protected:
/**
* Prepares the output. The result is passed back to the caller
* line-by-line (expectedTotal > 0) and/or as full chunk
* (expectedTotal = 0). The expected size is just a guess, be
* prepared to handle more output.
*
* Each chunk already includes the necessary line breaks (in
* particular after the last line when it contains the entire
* output). It may be modified by the callback.
*
* @param processName NULL means use the current process' name,
* empty means use none
*/
void formatLines(Level msglevel,
Level outputlevel,
const std::string *processName,
const std::string *prefix,
const char *format,
va_list args,
boost::function<void (std::string &chunk, size_t expectedTotal)> print);
private:
Level m_level;
/**
* Set by formatLines() before writing the first message if log
* level is debugging, together with printing a message that gives
* the local time.
*/
Timespec m_startTime;
};
/**
* Takes a logger and adds it to the stack
* as long as the instance exists.
*/
template<class L> class PushLogger : boost::noncopyable
{
Logger::Handle m_logger;
public:
PushLogger() {}
/**
* Can use Handle directly here.
*/
PushLogger(const Logger::Handle &logger) : m_logger(logger)
{
if (m_logger) {
Logger::addLogger(m_logger);
}
}
/**
* Take any type that a Handle constructor accepts, then use it as
* Handle.
*/
template <class M> PushLogger(M logger) : m_logger(Logger::Handle(logger))
{
if (m_logger) {
Logger::addLogger(m_logger);
}
}
~PushLogger() throw ()
{
if (m_logger) {
Logger::removeLogger(m_logger.get());
}
}
operator bool () const { return m_logger; }
void reset(const Logger::Handle &logger)
{
if (m_logger) {
Logger::removeLogger(m_logger.get());
}
m_logger = logger;
if (m_logger) {
Logger::addLogger(m_logger);
}
}
template<class M> void reset(M logger)
{
if (m_logger) {
Logger::removeLogger(m_logger.get());
}
m_logger = Logger::Handle(logger);
if (m_logger) {
Logger::addLogger(m_logger);
}
}
void reset()
{
if (m_logger) {
Logger::removeLogger(m_logger.get());
}
m_logger = Logger::Handle();
}
L *get() { return static_cast<L *>(m_logger.get()); }
L * operator -> () { return get(); }
};
/**
* Wraps Logger::message() in the current default logger.
* and adds file and line where the message comes from.
*
* This macro reverses _prefix and _level to avoid the situation where
* the compiler mistakes a NULL _prefix with the _format parameter
* (happened once while doing code refactoring).
*
* @TODO make source and line info optional for release
* @TODO add function name (GCC extension)
*/
#define SE_LOG(_prefix, _level, _format, _args...) \
SyncEvo::Logger::instance().message(_level, \
_prefix, \
__FILE__, \
__LINE__, \
NULL, \
_format, \
##_args);
#define SE_LOG_SHOW(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::SHOW, _format, ##_args)
#define SE_LOG_ERROR(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::ERROR, _format, ##_args)
#define SE_LOG_WARNING(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::WARNING, _format, ##_args)
#define SE_LOG_INFO(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::INFO, _format, ##_args)
#define SE_LOG_DEV(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::DEV, _format, ##_args)
#define SE_LOG_DEBUG(_prefix, _format, _args...) SE_LOG(_prefix, SyncEvo::Logger::DEBUG, _format, ##_args)
SE_END_CXX
#endif // INCL_LOGGING