summaryrefslogtreecommitdiffstats
path: root/src/lib/log/logger.h
blob: 78a82ff91b3749567e9964d910019e1af79797a5 (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
// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef LOGGER_H
#define LOGGER_H

#include <atomic>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <string>

#include <exceptions/exceptions.h>
#include <log/logger_level.h>
#include <log/message_types.h>
#include <log/log_formatter.h>
#include <log/output_option.h>

namespace isc {
namespace log {

namespace interprocess {
// Forward declaration to hide implementation details from normal
// applications.
class InterprocessSync;
}

/// \page LoggingApi Logging API
/// \section LoggingApiOverview Overview
/// Kea logging uses the concepts of the widely-used Java logging
/// package log4j (https://logging.apache.org/log4j/), albeit implemented
/// in C++ using an open-source port.  Features of the system are:
///
/// - Within the code objects - known as loggers - can be created and
/// used to log messages.  These loggers have names; those with the
/// same name share characteristics (such as output destination).
/// - Loggers have a hierarchical relationship, with each logger being
/// the child of another logger, except for the top of the hierarchy, the
/// root logger.  If a logger does not log a message, it is passed to the
/// parent logger.
/// - Messages can be logged at severity levels of FATAL, ERROR, WARN, INFO
/// or DEBUG.  The DEBUG level has further sub-levels numbered 0 (least
/// informative) to 99 (most informative).
/// - Each logger has a severity level set associated with it.  When a
/// message is logged, it is output only if it is logged at a level equal
/// to the logger severity level or greater, e.g. if the logger's severity
/// is WARN, only messages logged at WARN, ERROR or FATAL will be output.
///
/// \section LoggingApiLoggerNames Kea Logger Names
/// Within Kea, the root logger root logger is given the name of the
/// program (via the stand-alone function setRootLoggerName()). Other loggers
/// are children of the root logger and are named "<program>.<sublogger>".
/// This name appears in logging output, allowing users to identify both
/// the Kea program and the component within the program that generated
/// the message.
///
/// When creating a logger, the abbreviated name "<sublogger>" can be used;
/// the program name will be prepended to it when the logger is created.
/// In this way, individual libraries can have their own loggers without
/// worrying about the program in which they are used, but:
/// - The origin of the message will be clearly identified.
/// - The same component can have different options (e.g. logging severity)
/// in different programs at the same time.
///
/// \section LoggingApiLoggingMessages Logging Messages
/// Instead of embedding the text of messages within the code, each message
/// is referred to using a symbolic name.  The logging code uses this name as
/// a key in a dictionary from which the message text is obtained.  Such a
/// system allows for the optional replacement of message text at run time.
/// More details about the message dictionary (and the compiler used to create
/// the symbol definitions) can be found in other modules in the src/lib/log
/// directory.
///
/// \section LoggingApiImplementationIssues Implementation Issues
/// Owing to the way that the logging is implemented, notably that loggers can
/// be declared as static external objects, there is a restriction on the
/// length of the name of a logger component (i.e. the length of
/// the string passed to the Logger constructor) to a maximum of 31 characters.
/// There is no reason for this particular value other than limiting the amount
/// of memory used.  It is defined by the constant Logger::MAX_LOGGER_NAME_SIZE,
/// and can be made larger (or smaller) if so desired.

class LoggerImpl;   // Forward declaration of the implementation class

/// \brief Bad Interprocess Sync
///
/// Exception thrown if a bad InterprocessSync object (such as null) is
/// used.
class BadInterprocessSync : public isc::Exception {
public:
    BadInterprocessSync(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what)
    {}
};

/// \brief Logger Name Error
///
/// Exception thrown if a logger name is too short or too long.
class LoggerNameError : public isc::Exception {
public:
    LoggerNameError(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what)
    {}
};

/// \brief Logger Name is null
///
/// Exception thrown if a logger name is null
class LoggerNameNull : public isc::Exception {
public:
    LoggerNameNull(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what)
    {}
};

/// \brief Logging Not Initialized
///
/// Exception thrown if an attempt is made to access a logging function
/// if the logging system has not been initialized.
class LoggingNotInitialized : public isc::Exception {
public:
    LoggingNotInitialized(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what)
    {}
};

/// \brief Logger Class
///
/// This class is the main class used for logging.  Use comprises:
///
/// 1. Constructing a logger by instantiating it with a specific name. (If the
/// same logger is in multiple functions within a file, overhead can be
/// minimized by declaring it as a file-wide static variable.)
/// 2. Using the error(), info() etc. methods to log an error.  (However, it is
/// recommended to use the LOG_ERROR, LOG_INFO etc. macros defined in macros.h.
/// These will avoid the potentially-expensive evaluation of arguments if the
/// severity is such that the message will be suppressed.)

class Logger {
public:
    /// Maximum size of a logger name
    static const size_t MAX_LOGGER_NAME_SIZE = 31;

    /// \brief Constructor
    ///
    /// Creates/attaches to a logger of a specific name.
    ///
    /// \param name Name of the logger.  If the name is that of the root name,
    /// this creates an instance of the root logger; otherwise it creates a
    /// child of the root logger.
    ///
    /// \note The name of the logger may be no longer than MAX_LOGGER_NAME_SIZE
    /// else the program will throw an exception.  This restriction allows
    /// loggers to be declared statically: the name is stored in a fixed-size
    /// array to avoid the need to allocate heap storage during program
    /// initialization (which causes problems on some operating systems).
    ///
    /// \note Note also that there is no constructor taking a std::string. This
    /// minimizes the possibility of initializing a static logger with a
    /// string, so leading to problems mentioned above.
    Logger(const char* name) : loggerptr_(0), initialized_(false) {

        // Validate the name of the logger.
        if (name) {
            // Name not null, is it too short or too long?
            size_t namelen = std::strlen(name);
            if ((namelen == 0) || (namelen > MAX_LOGGER_NAME_SIZE)) {
                isc_throw(LoggerNameError, "'" << name << "' is not a valid "
                          << "name for a logger: valid names must be between 1 "
                          << "and " << MAX_LOGGER_NAME_SIZE << " characters in "
                          << "length");
            }
        } else {
            isc_throw(LoggerNameNull, "logger names may not be null");
        }

        // Do the copy, ensuring a trailing null in all cases.
        std::strncpy(name_, name, MAX_LOGGER_NAME_SIZE);
        name_[MAX_LOGGER_NAME_SIZE] = '\0';
    }

    /// \brief Destructor
    virtual ~Logger();

    /// \brief Version
    static std::string getVersion();

    /// \brief The formatter used to replace placeholders
    typedef isc::log::Formatter<Logger> Formatter;

    /// \brief Get Name of Logger
    ///
    /// \return The full name of the logger (including the root name)
    virtual std::string getName();

    /// \brief Set Severity Level for Logger
    ///
    /// Sets the level at which this logger will log messages.  If none is set,
    /// the level is inherited from the parent.
    ///
    /// \param severity Severity level to log
    /// \param dbglevel If the severity is DEBUG, this is the debug level.
    /// This can be in the range 1 to 100 and controls the verbosity.  A value
    /// outside these limits is silently coerced to the nearest boundary.
    virtual void setSeverity(isc::log::Severity severity, int dbglevel = 1);

    /// \brief Get Severity Level for Logger
    ///
    /// \return The current logging level of this logger.  In most cases though,
    /// the effective logging level is what is required.
    virtual isc::log::Severity getSeverity();

    /// \brief Get Effective Severity Level for Logger
    ///
    /// \return The effective severity level of the logger.  This is the same
    /// as getSeverity() if the logger has a severity level set, but otherwise
    /// is the severity of the parent.
    virtual isc::log::Severity getEffectiveSeverity();

    /// \brief Return DEBUG Level
    ///
    /// \return Current setting of debug level.  This is returned regardless of
    /// whether the severity is set to debug.
    virtual int getDebugLevel();

    /// \brief Get Effective Debug Level for Logger
    ///
    /// \return The effective debug level of the logger.  This is the same
    /// as getDebugLevel() if the logger has a debug level set, but otherwise
    /// is the debug level of the parent.
    virtual int getEffectiveDebugLevel();

    /// \brief Returns if Debug Message Should Be Output
    ///
    /// \param dbglevel Level for which debugging is checked.  Debugging is
    /// enabled only if the logger has DEBUG enabled and if the dbglevel
    /// checked is less than or equal to the debug level set for the logger.
    virtual bool isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL);

    /// \brief Is INFO Enabled?
    virtual bool isInfoEnabled();

    /// \brief Is WARNING Enabled?
    virtual bool isWarnEnabled();

    /// \brief Is ERROR Enabled?
    virtual bool isErrorEnabled();

    /// \brief Is FATAL Enabled?
    virtual bool isFatalEnabled();

    /// \brief Output Debug Message
    ///
    /// \param dbglevel Debug level, ranging between 0 and 99.  Higher numbers
    /// are used for more verbose output.
    /// \param ident Message identification.
    Formatter debug(int dbglevel, const MessageID& ident);

    /// \brief Output Informational Message
    ///
    /// \param ident Message identification.
    Formatter info(const MessageID& ident);

    /// \brief Output Warning Message
    ///
    /// \param ident Message identification.
    Formatter warn(const MessageID& ident);

    /// \brief Output Error Message
    ///
    /// \param ident Message identification.
    Formatter error(const MessageID& ident);

    /// \brief Output Fatal Message
    ///
    /// \param ident Message identification.
    Formatter fatal(const MessageID& ident);

    /// \brief Replace the interprocess synchronization object
    ///
    /// If this method is called with null as the argument, it throws a
    /// BadInterprocessSync exception.
    ///
    /// \note This method is intended to be used only within this log library
    /// and its tests.  Normal application shouldn't use it (in fact,
    /// normal application shouldn't even be able to instantiate
    /// InterprocessSync objects).
    ///
    /// \param sync The logger uses this synchronization object for
    /// synchronizing output of log messages. It should be deletable and
    /// the ownership is transferred to the logger. If null is passed,
    /// a BadInterprocessSync exception is thrown.
    void setInterprocessSync(isc::log::interprocess::InterprocessSync* sync);

    /// @brief Check if this logger has an appender of the given type.
    ///
    /// @param destination the appender type to be checked: console, file or syslog
    ///
    /// @return true if an appender of the given type is found, false otherwise
    bool hasAppender(OutputOption::Destination const destination);

    /// \brief Equality
    ///
    /// Check if two instances of this logger refer to the same stream.
    ///
    /// \return true if the logger objects are instances of the same logger.
    bool operator==(Logger& other);

private:
    friend class isc::log::Formatter<Logger>;

    /// \brief Raw output function
    ///
    /// This is used by the formatter to output formatted output.
    ///
    /// \param severity Severity of the message being output.
    /// \param message Text of the message to be output.
    void output(const Severity& severity, const std::string& message);

    /// \brief Copy Constructor
    ///
    /// Disabled (marked private) as it makes no sense to copy the logger -
    /// just create another one of the same name.
    Logger(const Logger&);

    /// \brief Assignment Operator
    ///
    /// Disabled (marked private) as it makes no sense to copy the logger -
    /// just create another one of the same name.
    Logger& operator=(const Logger&);

    /// \brief Initialize Implementation
    ///
    /// Returns the logger pointer.  If not yet set, the implementation class is
    /// initialized.
    ///
    /// The main reason for this is to allow loggers to be declared statically
    /// before the underlying logging system is initialized.  However, any
    /// attempt to access a logging method on any logger before initialization -
    /// regardless of whether is is statically or automatically declared -  will
    /// cause a "LoggingNotInitialized" exception to be thrown.
    ///
    /// \return Returns pointer to implementation
    LoggerImpl* getLoggerPtr();

    /// \brief Initialize Underlying Implementation and Set loggerptr_
    void initLoggerImpl();

    ///< Pointer to underlying logger
    LoggerImpl* loggerptr_;

    ///< Copy of the logger name
    char name_[MAX_LOGGER_NAME_SIZE + 1];

    ///< Mutex to protect the internal state
    std::mutex mutex_;

    ///< Flag which indicates if logger is initialized
    std::atomic<bool> initialized_;
};

} // namespace log
} // namespace isc


#endif // LOGGER_H