summaryrefslogtreecommitdiff
path: root/src/syncevo/Logging.h
blob: dde9e3255d703c1c76d9a0f7433c35f5b35cf0c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
/*
 * 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;

        MessageOptions(Level level);
        MessageOptions(Level level,
                       const std::string *prefix,
                       const char *file,
                       int line,
                       const char *function);
    };

    /**
     * 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