Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/syslog structured data #122

Merged
merged 7 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/eckit/log/SysLog.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "eckit/runtime/Main.h"

#include "eckit/log/TimeStamp.h"
#include "eckit/net/IPAddress.h"

namespace eckit {

Expand All @@ -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();
}
Expand All @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/eckit/log/SysLog.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand All @@ -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; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these being set?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the keywords that belongs to origin part of the syslog structural data described in RFC5424 -> https://datatracker.ietf.org/doc/html/rfc5424#section-7.2

void swVersion(const std::string& version) { swVersion_ = version; }
void enterpriseId(const std::string& id) { enterpriseId_ = id; }

private: // methods
void print(std::ostream& out) const;

Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/log/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
84 changes: 84 additions & 0 deletions tests/log/test_syslog.cc
Original file line number Diff line number Diff line change
@@ -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 <iostream>

#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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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);
}
Loading