--- /dev/null
+[func]* fdupont
+ Improved multiple line log records in forensic log files:
+ continuation lines, i.e. all lines before the last one of
+ the log record, get a hyphen instead a space after the
+ leading timestamp. This feature can be disabled e.g.
+ for backward compatibility by setting the new paramater
+ "mark-continuation-lines" to false in the hook library
+ configuration.
+ (Gitlab #4526)
directory which can be overridden at startup by setting the environment variable
``KEA_HOOK_SCRIPTS_PATH`` to a different path.
+Since Kea 3.3.0 a new parameter was added to make always possible to
+distinguish a multiple line log record from multiple log records:
+
+- ``mark-continuation-lines`` - when true (the default) continuation
+ lines (i.e. lines before the last one in the log record) get a hyphen
+ vs. a space after the timestamp.
+
Custom formatting can be enabled for logging information that can be extracted
either from the client's request packet or from the server's response packet.
Use with caution as this might affect server performance.
#include <errno.h>
#include <iostream>
+#include <list>
#include <set>
#include <sstream>
#include <time.h>
namespace legal_log {
RotatingFile::RotatingFile(const DatabaseConnection::ParameterMap& parameters)
- : LegalLogMgr(parameters), time_unit_(TimeUnit::Day), count_(1), timestamp_(0) {
+ : LegalLogMgr(parameters), time_unit_(TimeUnit::Day), count_(1),
+ timestamp_(0), mark_continuation_lines_(true) {
apply(parameters);
}
if (parameters.find("postrotate") != parameters.end()) {
postrotate = parameters.at("postrotate");
}
+ if (parameters.find("mark-continuation-lines") != parameters.end()) {
+ string mcl(parameters.at("mark-continuation-lines"));
+ // The parser sets "true" or "false" so do not check...
+ mark_continuation_lines_ = (mcl != "false");
+ }
path_ = path;
base_name_ = base;
time_unit_ = unit;
string timestamp = getNowString();
stringstream ss(text);
+ // Collect lines.
+ list<string> lines;
for (string line; getline(ss, line, '\n');) {
- file_ << timestamp << " " << line << endl;
+ lines.push_back(line);
+ }
+ while (!lines.empty()) {
+ string line = lines.front();
+ lines.pop_front();
+ file_ << timestamp;
+ if (mark_continuation_lines_ && !lines.empty()) {
+ file_ << "-";
+ } else {
+ file_ << " ";
+ }
+ file_ << line << endl;
}
int sav_error = errno;
if (!file_.good()) {
/// - prerotate
/// - postrotate
/// - count
+ /// - mark-continuation-lines
///
/// @param parameters A data structure relating keywords and values
/// concerned with the manager configuration.
/// @b postrotate - An external executable or script called with the name of the file that
/// was opened. Kea does not wait for the process to finish.
///
+ /// @b mark-continuation-lines - When true (default) mark continuation
+ /// lines so only the last line in a multiple line record gets a space
+ /// (vs hyphen) after the leading timestamp.
+ ///
/// @param parameters The library parameters.
void apply(const isc::db::DatabaseConnection::ParameterMap& parameters);
///
/// - @b EOL - the character(s) generated std::endl
///
+ /// When mark_continuation_lines_ is true (default) multiple lines give:
+ ///
+ /// "<timestamp>-<text1><EOL>"
+ /// "<timestamp>-<text2><EOL>"
+ /// "<timestamp>SP<text3><EOL>"
+ ///
/// @param addr Address or prefix (ignored).
/// @param text String to append.
///
/// @brief Mutex to protect output.
std::mutex mutex_;
+protected:
+ /// @brief The mark continuation lines flag.
+ bool mark_continuation_lines_;
+
public:
/// @brief Factory class method.
///
#include <gtest/gtest.h>
+#include <boost/pointer_cast.hpp>
+
using namespace std;
using namespace isc;
using namespace isc::asiolink;
LegalLogMgrFactory::instance()->setRequestFormatExpression(format);
+ // Disable mark continuation lines.
+ TestableRotatingFilePtr trfp =
+ boost::dynamic_pointer_cast<TestableRotatingFile>(LegalLogMgrFactory::instance());
+ ASSERT_TRUE(trfp);
+ trfp->setMarkContinuationLines(false);
+
int ret;
// Make a lease and add it to the callout arguments.
checkFileLines(genName(today()), today_now_string, lines);
}
+// Verifies that the custom format logs on a multiple line record.
+TEST_F(CalloutTestv4, customRequestLoggingFormatMultipleLineRecord) {
+ ASSERT_NO_THROW(LegalLogMgrFactory::instance().reset(new TestableRotatingFile(time_)));
+
+ // Make a callout handle
+ CalloutHandlePtr handle = getCalloutHandle(decline_);
+ handle->setCurrentLibrary(0);
+
+ std::string format = "ifelse(pkt4.msgtype == 4, 'first line' + 0x0a + 'second line', '')";
+
+ LegalLogMgrFactory::instance()->setRequestFormatExpression(format);
+
+ int ret;
+
+ // Make a lease and add it to the callout arguments.
+ Lease4Ptr lease4 = createLease4("192.2.1.100", 6735, hwaddr_, ClientIdPtr(), 1234);
+
+ // The callout should succeed and generate an entry for 192.2.1.100.
+ {
+ ScopedCalloutHandleState callout_handle_state(handle);
+ handle->setArgument("lease4", lease4);
+ handle->setArgument("query4", decline_);
+ ASSERT_NO_THROW(ret = lease4_decline(*handle));
+ EXPECT_EQ(0, ret);
+ }
+
+ // Close it to flush any unwritten data
+ LegalLogMgrFactory::instance()->close();
+
+ // Verify that the file content is correct.
+ std::vector<std::string>lines;
+ lines.push_back("first line");
+ lines.push_back("second line");
+
+ std::string today_now_string = LegalLogMgrFactory::instance()->getNowString();
+ // Use the continuation lines variant.
+ checkFileMultipleLines(genName(today()), today_now_string, lines);
+}
+
} // end of anonymous namespace
LegalLogMgrFactory::instance()->setRequestFormatExpression(format);
+ // Disable mark continuation lines.
+ TestableRotatingFilePtr trfp =
+ boost::dynamic_pointer_cast<TestableRotatingFile>(LegalLogMgrFactory::instance());
+ ASSERT_TRUE(trfp);
+ trfp->setMarkContinuationLines(false);
+
int ret;
// Make a lease and add it to the callout arguments.
checkFileLines(genName(today()), today_now_string, lines);
}
+// Verifies that the custom format logs on a multiple line record.
+TEST_F(CalloutTestv6, customRequestLoggingFormatMultipleLineRecord) {
+ ASSERT_NO_THROW(LegalLogMgrFactory::instance().reset(new TestableRotatingFile(time_)));
+
+ CfgMgr::instance().setFamily(AF_INET6);
+
+ // Make a callout handle
+ CalloutHandlePtr handle = getCalloutHandle(decline_);
+ handle->setCurrentLibrary(0);
+
+ std::string format = "ifelse(pkt6.msgtype == 9, 'first line' + 0x0a + 'second line', '')";
+
+ LegalLogMgrFactory::instance()->setRequestFormatExpression(format);
+
+ int ret;
+
+ // Make a lease and add it to the callout arguments.
+ Lease6Ptr lease6 = createLease6(duid_, Lease::TYPE_NA, "2001:db8:1::", 128,
+ 713, HWAddrPtr());
+
+ // The callout should succeed and generate an entry for 2001:db8:1::
+ {
+ ScopedCalloutHandleState callout_handle_state(handle);
+ handle->setArgument("lease6", lease6);
+ ASSERT_NO_THROW(ret = lease6_decline(*handle));
+ EXPECT_EQ(0, ret);
+ }
+
+ {
+ ScopedCalloutHandleState callout_handle_state(handle);
+ handle->setArgument("query6", decline_);
+ handle->setArgument("response6", response_);
+ ASSERT_NO_THROW(ret = pkt6_send(*handle));
+ EXPECT_EQ(0, ret);
+ }
+
+ // Close it to flush any unwritten data
+ LegalLogMgrFactory::instance()->close();
+
+ // Verify that the file content is correct.
+ std::vector<std::string>lines;
+ lines.push_back("first line");
+ lines.push_back("second line");
+
+ std::string today_now_string = LegalLogMgrFactory::instance()->getNowString();
+ // Use the continuation lines variant.
+ checkFileMultipleLines(genName(today()), today_now_string, lines);
+}
+
TEST_F(CalloutTestv6, multipleAddressesAndPrefixesCustomLoggingFormatRequestOnly) {
ASSERT_NO_THROW(LegalLogMgrFactory::instance().reset(new TestableRotatingFile(time_)));
file_list_.insert(getFileName());
}
+ /// @brief Sets the mark continuation lines flag.
+ void setMarkContinuationLines(bool mark_continuation_lines) {
+ mark_continuation_lines_ = mark_continuation_lines;
+ }
+
/// @brief Sets the override date value
///
/// @param new value for the override date
<< file_name;
}
+ /// @brief Check a file's contents against a multiple-line record
+ ///
+ /// Passes if the given file's content matches. Fails otherwise.
+ ///
+ /// @param file_name name of the file to read
+ /// @param expected_lines a vector of the lines expected to be found
+ /// in the file (entries DO NOT include EOL) representing a multiple
+ /// line record so continuation lines until the last one.
+ void checkFileMultipleLines(const string& file_name,
+ const string& now_string,
+ const vector<string>& expected_lines) {
+ ifstream is;
+ is.open(file_name.c_str());
+ ASSERT_TRUE(is.good()) << "Could not open file: " << file_name;
+
+ unsigned i = 0;
+ while (!is.eof()) {
+ char buf[1024];
+
+ is.getline(buf, sizeof(buf));
+ if (is.gcount() > 0) {
+ ASSERT_TRUE(i <= expected_lines.size())
+ << "Too many entries in file: " << file_name;
+ string cmp_line = now_string;
+ if (i + 1 == expected_lines.size()) {
+ cmp_line += " ";
+ } else {
+ cmp_line += "-";
+ }
+ cmp_line += expected_lines[i];
+ ASSERT_EQ(cmp_line, buf) << "line mismatch in: " << file_name
+ << " at line:" << i;
+
+ ++i;
+ }
+ }
+
+ ASSERT_EQ(i, expected_lines.size()) << "Not enough entries in file: "
+ << file_name;
+ }
+
/// @brief Check that the file was not created.
///
/// Passes if the given file does not exist. Fails otherwise.
file_parameters[key] = boost::lexical_cast<string>(integer_value);
}
}
+
+ // bool
+ for (char const* const& key : { "mark-continuation-lines" }) {
+ ConstElementPtr const value(parameters->get(key));
+ if (value) {
+ file_parameters.emplace(key,
+ value->boolValue() ? "true" : "false");
+ }
+ }
map = file_parameters;
}
/// - prerotate
/// - postrotate
/// - count
+ /// - mark-continuation-lines
/// - syslog parameters:
/// - pattern
/// - facility
/// - prerotate
/// - postrotate
/// - count
+ /// - mark-continuation-lines
///
/// @param parameters The library parameters.
/// @param [out] map The parameter map.