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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
// 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/.

#include <config.h>

#include <stdio.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <unistd.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <fstream><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <iostream><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <string><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <gtest/gtest.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <boost/scoped_array.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/lexical_cast.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <exceptions/exceptions.h>

#include <log/macros.h>
#include <log/log_messages.h>
#include <log/logger.h>
#include <log/logger_level.h>
#include <log/logger_manager.h>
#include <log/logger_name.h>
#include <log/logger_specification.h>
#include <log/message_initializer.h>
#include <log/output_option.h>
#include <log/tests/tempdir.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <testutils/gtest_utils.h>

#include <sys/types.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <regex.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc;
using namespace isc::log;
using namespace std;

/// \brief LoggerManager Test
class LoggerManagerTest : public ::testing::Test {
public:
    LoggerManagerTest() {
        // Initialization of logging is done in main()
    }

    ~LoggerManagerTest() {
        LoggerManager::reset();
    }
};

// Convenience class to create the specification for the logger "filelogger",
// which, as the name suggests, logs to a file.  It remembers the file name and
// deletes the file when instance of the class is destroyed.
class SpecificationForFileLogger {
public:

    // Constructor - allocate file and create the specification object
    SpecificationForFileLogger() : spec_(), name_(createTempFilename()),
                                   logname_("filelogger") {

        // Set the output to a temporary file.
        OutputOption option;
        option.destination = OutputOption::DEST_FILE;
        option.filename = name_;

        // Set target output to the file logger.  The defaults indicate
        // INFO severity.
        spec_.setName(logname_);
        spec_.addOutputOption(option);
    }

    // Destructor, remove the file.  This is only a test, so ignore failures
    ~SpecificationForFileLogger() {
        if (! name_.empty()) {
            static_cast<void>(remove(name_.c_str()));

            // Depending on the log4cplus version, a lock file may also be
            // created.
            static_cast<void>(remove((name_ + ".lock").c_str()));
        }
    }

    // Return reference to the logging specification for this loggger
    LoggerSpecification& getSpecification() {
        return spec_;
    }

    // Return name of the logger
    string getLoggerName() const {
        return logname_;
    }

    // Return name of the file
    string getFileName() const {
        return name_;
    }

    // Create temporary filename
    //
    // The compiler warns against tmpnam() and suggests mkstemp instead.
    // Unfortunately, this creates the filename and opens it.  So we need to
    // close and delete the file before returning the name.  Also, the name
    // is based on the template supplied and the name of the temporary
    // directory may vary between systems.  So translate TMPDIR and if that
    // does not exist, use /tmp.
    //
    // \return Temporary file name
    static std::string createTempFilename() {
        string filename = TEMP_DIR + "/kea_logger_manager_test_XXXXXX";

        // Copy into writable storage for the call to mkstemp
        boost::scoped_array<char> tname(new char[filename.size() + 1]);
        strcpy(tname.get(), filename.c_str());

        // Create file, close and delete it, and store the name for later.
        // There is still a race condition here, albeit a small one.
        int filenum = mkstemp(tname.get());
        if (filenum == -1) {
            isc_throw(Exception, "Unable to obtain unique filename");
        }
        close(filenum);

        return (string(tname.get()));
    }


private:
    LoggerSpecification     spec_;      // Specification for this file logger
    string                  name_;      // Name of the output file
    string                  logname_;   // Name of this logger
};

// Convenience function to read an output log file and check that each line
// contains the expected message ID
//
// \param filename Name of the file to check
// \param start Iterator pointing to first expected message ID
// \param finish Iterator pointing to last expected message ID
template <typename T>
void checkFileContents(const std::string& filename, T start, T finish) {

    // Access the file for input
    ifstream infile(filename.c_str());
    if (! infile.good()) {
        FAIL() << "Unable to open the logging file " << filename;
    }

    // Iterate round the expected message IDs and check that they appear in
    // the string.
    string line;    // Line read from the file

    T i = start;    // Iterator
    getline(infile, line);
    int lineno = 1;

    while ((i != finish) && (infile.good())) {

        // Check that the message ID appears in the line.
        EXPECT_TRUE(line.find(string(*i)) != string::npos)
            << "Expected to find " << string(*i) << " on line " << lineno
            << " of logging file " << filename;

        // Go for the next line
        ++i;
        getline(infile, line);
        ++lineno;
    }

    // Why did the loop end?
    EXPECT_TRUE(i == finish) << "Did not reach the end of the message ID list";
    EXPECT_TRUE(infile.eof()) << "Did not reach the end of the logging file";

    // File will close when the instream is deleted at the end of this
    // function.
}

// Check that the logger correctly creates something logging to a file.
TEST_F(LoggerManagerTest, FileLogger) {

    // Create a specification for the file logger and use the manager to
    // connect the "filelogger" logger to it.
    SpecificationForFileLogger file_spec;

    // For the first test, we want to check that the file is created
    // if it does not already exist.  So delete the temporary file before
    // logging the first message.
    static_cast<void>(remove(file_spec.getFileName().c_str()));

    // Set up the file appenders.
    LoggerManager manager;
    manager.process(file_spec.getSpecification());

    // Try logging to the file.  Local scope is set to ensure that the logger
    // is destroyed before we reset the global logging.  We record what we
    // put in the file for a later comparison.
    vector<MessageID> ids;
    {

        // Scope-limit the logger to ensure it is destroyed after the brief
        // check.  This adds weight to the idea that the logger will not
        // keep the file open.
        Logger logger(file_spec.getLoggerName().c_str());

        LOG_FATAL(logger, LOG_DUPLICATE_MESSAGE_ID).arg("test");
        ids.push_back(LOG_DUPLICATE_MESSAGE_ID);

        LOG_FATAL(logger, LOG_DUPLICATE_NAMESPACE).arg("test");
        ids.push_back(LOG_DUPLICATE_NAMESPACE);
    }
    LoggerManager::reset();

    // At this point, the output file should contain two lines with messages
    // LOG_DUPLICATE_MESSAGE_ID and LOG_DUPLICATE_NAMESPACE messages - test this.
    checkFileContents(file_spec.getFileName(), ids.begin(), ids.end());

    // Re-open the file (we have to assume that it was closed when we
    // reset the logger - there is no easy way to check) and check that
    // new messages are appended to it.  We use the alternative
    // invocation of process() here to check it works.
    vector<LoggerSpecification> spec(1, file_spec.getSpecification());
    manager.process(spec.begin(), spec.end());

    // Create a new instance of the logger and log three more messages.
    Logger logger(file_spec.getLoggerName().c_str());

    LOG_FATAL(logger, LOG_NO_SUCH_MESSAGE).arg("test");
    ids.push_back(LOG_NO_SUCH_MESSAGE);

    LOG_FATAL(logger, LOG_INVALID_MESSAGE_ID).arg("test").arg("test2");
    ids.push_back(LOG_INVALID_MESSAGE_ID);

    LOG_FATAL(logger, LOG_NO_MESSAGE_ID).arg("42");
    ids.push_back(LOG_NO_MESSAGE_ID);

    // Close the file and check again
    LoggerManager::reset();
    checkFileContents(file_spec.getFileName(), ids.begin(), ids.end());
}

// Check if the file rolls over when it gets above a certain size.
TEST_F(LoggerManagerTest, FileSizeRollover) {
    // Set to a suitable minimum that log4cplus can cope with.
    static const size_t SIZE_LIMIT = 204800;

    // Set up the name of the file.
    SpecificationForFileLogger file_spec;
    LoggerSpecification& spec = file_spec.getSpecification();

    // Expand the option to ensure that a maximum version size is set.
    LoggerSpecification::iterator opt = spec.begin();
    EXPECT_TRUE(opt != spec.end());
    opt->maxsize = SIZE_LIMIT;    // Bytes
    opt->maxver = 2;

    // The current output file does not exist (the creation of file_spec
    // ensures that.  Check that previous versions don't either.
    vector<string> prev_name;
    for (int i = 0; i < 3; ++i) {
        prev_name.push_back(file_spec.getFileName() + "." +
                            boost::lexical_cast<string>(i + 1));
        (void) remove(prev_name[i].c_str());
    }

    // Generate an argument for a message that ensures that the message when
    // logged will be over that size.
    string big_arg(SIZE_LIMIT, 'x');

    // Set up the file logger
    LoggerManager manager;
    manager.process(spec);

    // Log the message twice using different message IDs.  This should generate
    // three files as for the log4cplus implementation, the files appear to
    // be rolled after the message is logged.
    {
        Logger logger(file_spec.getLoggerName().c_str());
        LOG_FATAL(logger, LOG_NO_SUCH_MESSAGE).arg(big_arg);
        LOG_FATAL(logger, LOG_DUPLICATE_NAMESPACE).arg(big_arg);
    }

    // Check them.
    LoggerManager::reset();     // Ensure files are closed

    vector<MessageID> ids;
    ids.push_back(LOG_NO_SUCH_MESSAGE);
    checkFileContents(prev_name[1], ids.begin(), ids.end());

    ids.clear();
    ids.push_back(LOG_DUPLICATE_NAMESPACE);
    checkFileContents(prev_name[0], ids.begin(), ids.end());

    // Log another message and check that the files have rotated and that
    // a .3 version does not exist.
    manager.process(spec);
    {
        Logger logger(file_spec.getLoggerName().c_str());
        LOG_FATAL(logger, LOG_NO_MESSAGE_TEXT).arg("42").arg(big_arg);
    }

    LoggerManager::reset();     // Ensure files are closed

    // Check that the files have moved.
    ids.clear();
    ids.push_back(LOG_DUPLICATE_NAMESPACE);
    checkFileContents(prev_name[1], ids.begin(), ids.end());

    ids.clear();
    ids.push_back(LOG_NO_MESSAGE_TEXT);
    checkFileContents(prev_name[0], ids.begin(), ids.end());

    // ... and check that the .3 version does not exist.
    ifstream file3(prev_name[2].c_str(), ifstream::in);
    EXPECT_FALSE(file3.good());

    // Tidy up
    for (vector<string>::size_type i = 0; i < prev_name.size(); ++i) {
       (void) remove(prev_name[i].c_str());
    }
}

// Check if an exception is thrown if maxsize is too large.
TEST_F(LoggerManagerTest, TooLargeMaxsize) {
    // Set up the name of the file.
    SpecificationForFileLogger file_spec;
    LoggerSpecification& spec(file_spec.getSpecification());

    // UINT64_MAX should be large enough.
    LoggerSpecification::iterator opt = spec.begin();
    EXPECT_TRUE(opt != spec.end());
    opt->maxsize = std::numeric_limits<uint64_t>::max();  // bytes

    // Set up the file logger.
    LoggerManager manager;
    EXPECT_THROW_MSG(manager.process(spec), BadValue,
                     "expected maxsize < 2147483647MB, but instead got "
                     "18446744073709MB");

    opt->maxsize = 1000000LL * (std::numeric_limits<int32_t>::max() + 1LL);  // bytes
    EXPECT_THROW_MSG(manager.process(spec), BadValue,
                     "expected maxsize < 2147483647MB, but instead got "
                     "2147483648MB");
}

namespace { // begin unnamed namespace

// When we begin to use C++11, we could replace use of POSIX API with
// <regex>.

class RegexHolder {
public:
    RegexHolder(const char* expr, const int flags = REG_EXTENDED) {
        const int rc = regcomp(&regex_, expr, flags);
        if (rc) {
            regfree(&regex_);
            throw;<--- Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow. If there is no current exception this calls std::terminate(). More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object
        }
    }

    ~RegexHolder() {
        regfree(&regex_);
    }

    regex_t* operator*() {
        return (&regex_);
    }

private:
    regex_t regex_;
};

} // end of unnamed namespace

// Check that the logger correctly outputs the full formatted layout
// pattern.
TEST_F(LoggerManagerTest, checkLayoutPattern) {
    // Create a specification for the file logger and use the manager to
    // connect the "filelogger" logger to it.
    SpecificationForFileLogger file_spec;

    // For the first test, we want to check that the file is created
    // if it does not already exist.  So delete the temporary file before
    // logging the first message.
    static_cast<void>(remove(file_spec.getFileName().c_str()));

    // Set up the file appenders.
    LoggerManager manager;
    manager.process(file_spec.getSpecification());

    // Try logging to the file.  Local scope is set to ensure that the logger
    // is destroyed before we reset the global logging.
    {
        Logger logger(file_spec.getLoggerName().c_str());
        LOG_FATAL(logger, LOG_DUPLICATE_MESSAGE_ID).arg("test");
    }

    LoggerManager::reset();

    // Access the file for input
    const std::string& filename = file_spec.getFileName();
    ifstream infile(filename.c_str());
    if (! infile.good()) {
        FAIL() << "Unable to open the logging file " << filename;
    }

    std::string line;
    std::getline(infile, line);

    RegexHolder regex(// %D{%Y-%m-%d %H:%M:%S.%q}
                      "^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}[[:space:]]"
                      "[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\.[[:digit:]]+[[:space:]]"
                      // %-5p
                      "[[:alpha:]]{1,5}[[:space:]]"
                      // [%c/%i/%t]
                      "\\[[[:alnum:]\\-\\.]+/[[:digit:]]+\\.(0x)?[[:xdigit:]]+\\][[:space:]]"
                      );

    const int re = regexec(*regex, line.c_str(), 0, NULL, 0);
    ASSERT_EQ(0, re)
        << "Logged message does not match expected layout pattern";
}

// Check that after calling the logDuplicatedMessages, the duplicated
// messages are removed.
TEST_F(LoggerManagerTest, logDuplicatedMessages) {
    // Original set should not have duplicates.
    ASSERT_EQ(0, MessageInitializer::getDuplicates().size());

    // This just defines 1, but we'll add it a number of times.
    const char* dupe[] = {
        "DUPE", "dupe",
        NULL
    };
    const MessageInitializer init_message_initializer_1(dupe);
    const MessageInitializer init_message_initializer_2(dupe);

    MessageInitializer::loadDictionary();
    // Should have a duplicate now.
    ASSERT_EQ(1, MessageInitializer::getDuplicates().size());

    // The logDuplicatedMessages, besides logging, should also remove the
    // duplicates.
    LoggerManager::logDuplicatedMessages();
    ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
}

// Check that output options can be inherited.
TEST_F(LoggerManagerTest, outputOptionsInheritance) {
    LoggerManager manager;
    SpecificationForFileLogger file_spec;
    vector<LoggerSpecification> specs;

    // Create the root logger configuration with a file output option.
    string root_name(getRootLoggerName());
    LoggerSpecification root_spec(root_name);
    OutputOption root_option;
    root_option.destination = OutputOption::DEST_FILE;
    root_option.filename = file_spec.getFileName();
    root_option.pattern = "%p %m\n";
    root_spec.addOutputOption(root_option);
    specs.push_back(root_spec);

    // Create a child logger configuration without any output options.
    // It should inherit the output option from the root logger.
    string foo_name(root_name + ".foo");
    LoggerSpecification foo_spec(foo_name);
    specs.push_back(foo_spec);

    // Create another child logger configuration with a console output option.
    string bar_name(root_name + ".bar");
    LoggerSpecification bar_spec(bar_name);
    OutputOption bar_option;
    bar_option.destination = OutputOption::DEST_CONSOLE;
    bar_option.pattern = "%p %m\n";
    bar_spec.addOutputOption(bar_option);
    specs.push_back(bar_spec);

    // Check the number of output options for each specification.
    EXPECT_EQ(root_spec.optionCount(), 1);
    EXPECT_EQ(foo_spec.optionCount(), 0);
    EXPECT_EQ(bar_spec.optionCount(), 1);

    // Process all the specifications.
    manager.process(specs.begin(), specs.end());

    // Log two messages each.
    Logger root_logger(root_name.c_str());
    Logger foo_logger(foo_name.c_str());
    Logger bar_logger(bar_name.c_str());
    LOG_INFO(root_logger, "from root logger 1");
    LOG_INFO(foo_logger, "from foo logger 1");
    LOG_INFO(bar_logger, "from bar logger 1");
    LOG_INFO(root_logger, "from root logger 2");
    LOG_INFO(foo_logger, "from foo logger 2");
    LOG_INFO(bar_logger, "from bar logger 2");

    // Check that root and foo were logged to file and that bar which
    // had an explicit console configuration did not.
    std::ifstream ifs(file_spec.getFileName());
    std::stringstream s;
    s << ifs.rdbuf();
    std::string const result(s.str());
    EXPECT_NE(result.find("INFO from root logger 1"), string::npos);
    EXPECT_NE(result.find("INFO from foo logger 1"), string::npos);
    EXPECT_EQ(result.find("INFO from bar logger 1"), string::npos);
    EXPECT_NE(result.find("INFO from root logger 2"), string::npos);
    EXPECT_NE(result.find("INFO from foo logger 2"), string::npos);
    EXPECT_EQ(result.find("INFO from bar logger 2"), string::npos);
}