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
|
// Copyright (C) 2011, 2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <stdio.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <string>
#include <gtest/gtest.h>
#include <boost/scoped_array.hpp>
#include <boost/lexical_cast.hpp>
#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_specification.h>
#include <log/message_initializer.h>
#include <log/output_option.h>
#include "tempdir.h"
#include <sys/types.h>
#include <regex.h>
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 writeable 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 copy 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 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());
}
}
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(®ex_, expr, flags);
if (rc) {
regfree(®ex_);
throw;
}
}
~RegexHolder() {
regfree(®ex_);
}
regex_t* operator*() {
return (®ex_);
}
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]
"\\[[[:alnum:]\\-\\.]+/[[:digit:]]+\\][[: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());
}
|