diff --git a/src/eckit/log/SysLog.cc b/src/eckit/log/SysLog.cc index 98c5ffc54..e10304ad3 100644 --- a/src/eckit/log/SysLog.cc +++ b/src/eckit/log/SysLog.cc @@ -18,6 +18,7 @@ #include "eckit/runtime/Main.h" #include "eckit/log/TimeStamp.h" +#include "eckit/net/IPAddress.h" namespace eckit { @@ -43,11 +44,26 @@ int SysLog::procid() const { return ::getpid(); } + std::string SysLog::structuredData() const { + if (software_.empty() && swVersion_.empty() && enterpriseId_.empty()) { + return std::string(1, nilvalue()); + } + // RFC 5424 section 6.3 (only origin) std::ostringstream s; - - /// @todo Implement the structured message meta-description as described in RFC5424 secion 6.3 - s << nilvalue(); + std::string ip = net::IPAddress::myIPAddress().asString(); + + s << "[origin ip=\"" << ip << "\""; + if (!enterpriseId_.empty()) { + s << " enterpriseId=\"" << enterpriseId_ << "\""; + } + if (!software_.empty()) { + s << " software=\"" << software_ << "\""; + if (!swVersion_.empty()) { + s << " swVersion=\"" << swVersion_ << "\""; + } + } + s << "]"; return s.str(); } @@ -59,7 +75,7 @@ SysLog::operator std::string() const { static char sep = ' '; os // RFC 5424 section 6.2 (Header) - << "<" << priotity() << ">" << version() << sep << timestamp() << sep << fqdn() << sep << appName() << sep + << "<" << priority() << ">" << version() << sep << timestamp() << sep << fqdn() << sep << appName() << sep << procid() << sep << msgid() << sep // RFC 5424 section 6.3 diff --git a/src/eckit/log/SysLog.h b/src/eckit/log/SysLog.h index 28931e295..d88c31f53 100644 --- a/src/eckit/log/SysLog.h +++ b/src/eckit/log/SysLog.h @@ -72,7 +72,7 @@ class SysLog { SysLog(const std::string& msg, int msgid = 0, Facility f = SysLog::User, Severity s = SysLog::Info); - unsigned priotity() const { return facility_ * 8 + severity_; } + unsigned priority() const { return facility_ * 8 + severity_; } unsigned version() const { return 1; } @@ -99,6 +99,11 @@ class SysLog { return s; } + /// Optional fields for structured data (RFC 5424 section 6.3) + void software(const std::string& software) { software_ = software; } + void swVersion(const std::string& version) { swVersion_ = version; } + void enterpriseId(const std::string& id) { enterpriseId_ = id; } + private: // methods void print(std::ostream& out) const; @@ -112,6 +117,11 @@ class SysLog { int msgid_; std::string msg_; + + // optional fields for structured data + std::string software_; + std::string swVersion_; + std::string enterpriseId_; }; } // namespace eckit diff --git a/tests/log/CMakeLists.txt b/tests/log/CMakeLists.txt index fbd95d313..58f9346bc 100644 --- a/tests/log/CMakeLists.txt +++ b/tests/log/CMakeLists.txt @@ -27,3 +27,7 @@ ecbuild_add_test( TARGET eckit_test_log_user_channels ENABLED OFF SOURCES test_log_user_channels.cc LIBS eckit ) + +ecbuild_add_test( TARGET eckit_test_syslog + SOURCES test_syslog.cc + LIBS eckit ) \ No newline at end of file diff --git a/tests/log/test_syslog.cc b/tests/log/test_syslog.cc new file mode 100644 index 000000000..ab602a23a --- /dev/null +++ b/tests/log/test_syslog.cc @@ -0,0 +1,84 @@ +/* + * (C) Copyright 1996- ECMWF. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +#include + +#include "eckit/log/SysLog.h" +#include "eckit/testing/Test.h" + +using namespace std; +using namespace eckit; +using namespace eckit::testing; + +namespace eckit::test { + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("test_priority") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + std::string logString = static_cast(log); + std::string expectedPriority = "<" + std::to_string(log.priority()) + ">"; + EXPECT(logString.find(expectedPriority) != std::string::npos); +} + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("test_timezone") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + std::string logString = static_cast(log); + // Check if 'Z' UTC indicator is present + EXPECT(logString.find("Z") != std::string::npos); + // Check if 'T' separator is present + EXPECT(logString.find("T") != std::string::npos); +} + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("test_appname") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + EXPECT(log.appName() == Main::instance().name()); + + // Change the appName and check if it persists + log.appName("test_app"); + std::string logString = static_cast(log); + EXPECT(logString.find("test_app") != std::string::npos); + + // Create a new SysLog instance and check if it retains the original appName + SysLog newLog("New message", 2, SysLog::Local7, SysLog::Info); + EXPECT(newLog.appName() == Main::instance().name()); +} + +//---------------------------------------------------------------------------------------------------------------------- + +CASE("test_without_structured_data") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + std::string logString = static_cast(log); + // Check if structured data is not present + EXPECT(logString.find("[origin") == std::string::npos); +} + +//---------------------------------------------------------------------------------------------------------------------- +CASE("test_with_structured_data") { + SysLog log("Test message", 0, SysLog::Local7, SysLog::Info); + log.software("log_test"); + log.swVersion("1.0.0"); + log.enterpriseId("7464"); + std::string logString = static_cast(log); + EXPECT(logString.find("enterpriseId=\"7464\"") != std::string::npos); + EXPECT(logString.find("software=\"log_test\"") != std::string::npos); + EXPECT(logString.find("swVersion=\"1.0.0\"") != std::string::npos); +} +//---------------------------------------------------------------------------------------------------------------------- + +} // namespace eckit::test + +int main(int argc, char** argv) { + return run_tests(argc, argv); +}