summaryrefslogtreecommitdiff
path: root/src/syncevo/Logging.h
blob: 283efa8d4f3097deecf56b2f5bf8c71b8cde6037 (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
/*
 * 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 <boost/function.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) { m_processName = name; }
    static std::string getProcessName() { return m_processName; }

#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();

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

    /** redirect into messagev() */
    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
        ;

    /**
     * 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 Logger &instance();

    /**
     * Overrides the default Logger implementation. The Logger class
     * itself will never delete the active logger.
     *
     * @param logger    will be used for all future logging activities
     */
    static void pushLogger(Logger *logger);

    /**
     * Remove the current logger and restore previous one.
     * Must match a pushLogger() call.
     */
    static void popLogger();

    /** total number of active loggers */
    static int numLoggers();

    /**
     * access to active logger
     * @param index    0 for oldest (inner-most) logger
     * @return pointer or NULL for invalid index
     */
    static Logger *loggerAt(int index);

    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:
    static std::string m_processName;
    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;
};


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